diff --git a/smash/web/api_views/serialization_utils.py b/smash/web/api_views/serialization_utils.py
index 89e2c9c19c2eb5aa958a17165fe12d440b842c58..5f6cd27120fd039257378a8b3b83cedcc55b1d8e 100644
--- a/smash/web/api_views/serialization_utils.py
+++ b/smash/web/api_views/serialization_utils.py
@@ -10,6 +10,15 @@ def bool_to_yes_no(val):
         return "NO"
 
 
+def bool_to_yes_no_null(val):
+    if val is None:
+        return "N/A"
+    if val:
+        return "YES"
+    else:
+        return "NO"
+
+
 def flying_team_to_str(flying_team):
     result = ""
     if flying_team is not None:
diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py
index c31d13903ab43b26fa7db0fd1f9c570d28e5cb5e..cd7b5ac5dd59429c1457ea3fd30d5f90a4b87c0a 100644
--- a/smash/web/api_views/subject.py
+++ b/smash/web/api_views/subject.py
@@ -1,12 +1,12 @@
 import logging
 
-from django.urls import reverse
 from django.db.models import Count, Case, When, Min, Max
 from django.db.models import Q
 from django.http import JsonResponse
+from django.urls import reverse
 
 from web.api_views.serialization_utils import bool_to_yes_no, flying_team_to_str, location_to_str, add_column, \
-    serialize_date, serialize_datetime, get_filters_for_data_table_request
+    serialize_date, serialize_datetime, get_filters_for_data_table_request, bool_to_yes_no_null
 from web.models import StudySubject, Visit, Appointment, Subject, SubjectColumns, StudyColumns, Study, ContactAttempt
 from web.models.constants import SUBJECT_TYPE_CHOICES, GLOBAL_STUDY_ID
 from web.models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT, \
@@ -73,12 +73,25 @@ def get_subject_columns(request, subject_list_type):
     add_column(result, "Next of keen", "next_of_keen_name", subject_columns, "string_filter")
     add_column(result, "Next of keen phone", "next_of_keen_phone", subject_columns, "string_filter")
     add_column(result, "Next of keen address", "next_of_keen_address", subject_columns, "string_filter")
-    add_column(result, "Brain donation agreement", "brain_donation_agreement", study_subject_columns, "yes_no_filter", study.columns)
+    add_column(result, "Brain donation agreement", "brain_donation_agreement", study_subject_columns, "yes_no_filter",
+               study.columns)
     add_column(result, "Excluded", "excluded", study_subject_columns, "yes_no_filter", study.columns)
     add_column(result, "Info sent", "information_sent", study_subject_columns, "yes_no_filter", study.columns)
+
+    add_column(result, "Visit 1 virus", "virus_test_1", study_subject_columns, "yes_no_null_filter", study.columns)
+    add_column(result, "Visit 1 virus date", "virus_test_1_updated", study_subject_columns, None, study.columns)
+    add_column(result, "Visit 2 virus", "virus_test_2", study_subject_columns, "yes_no_null_filter", study.columns)
+    add_column(result, "Visit 2 virus date", "virus_test_2_updated", study_subject_columns, None, study.columns)
+    add_column(result, "Visit 3 virus", "virus_test_3", study_subject_columns, "yes_no_null_filter", study.columns)
+    add_column(result, "Visit 3 virus date", "virus_test_3_updated", study_subject_columns, None, study.columns)
+    add_column(result, "Visit 4 virus", "virus_test_4", study_subject_columns, "yes_no_null_filter", study.columns)
+    add_column(result, "Visit 4 virus date", "virus_test_4_updated", study_subject_columns, None, study.columns)
+    add_column(result, "Visit 5 virus", "virus_test_5", study_subject_columns, "yes_no_null_filter", study.columns)
+    add_column(result, "Visit 5 virus date", "virus_test_5_updated", study_subject_columns, None, study.columns)
+
     add_column(result, "Type", "type", study_subject_columns, "type_filter", study.columns)
     add_column(result, "Edit", "edit", None, None, sortable=False)
-    for visit_number in range(1, study.visits_to_show_in_subject_list+1):
+    for visit_number in range(1, study.visits_to_show_in_subject_list + 1):
         visit_key = "visit_" + str(visit_number)
         add_column(result, "Visit " + str(visit_number), visit_key, None, "visit_filter",
                    visible_param=study_subject_list.visits)
@@ -133,7 +146,8 @@ def get_subjects_order(subjects_to_be_ordered, order_column, order_direction, co
         else:
             pattern = column_filters[u'screening_number']
         result = subjects_to_be_ordered.all()
-        result = sorted(result, key=lambda t: t.sort_matched_screening_first(pattern, reverse=order_direction == '-'), reverse = order_direction == '-' )
+        result = sorted(result, key=lambda t: t.sort_matched_screening_first(pattern, reverse=order_direction == '-'),
+                        reverse=order_direction == '-')
     elif order_column == "default_location":
         result = subjects_to_be_ordered.order_by(order_direction + 'default_location')
     elif order_column == "flying_team":
@@ -173,6 +187,26 @@ def get_subjects_order(subjects_to_be_ordered, order_column, order_direction, co
     elif str(order_column).startswith("visit_"):
         visit_number = get_visit_number_from_visit_x_string(order_column)
         result = order_by_visit(subjects_to_be_ordered, order_direction, visit_number)
+    elif order_column == "virus_test_1":
+        result = subjects_to_be_ordered.order_by(order_direction + 'virus_test_1')
+    elif order_column == "virus_test_2":
+        result = subjects_to_be_ordered.order_by(order_direction + 'virus_test_2')
+    elif order_column == "virus_test_3":
+        result = subjects_to_be_ordered.order_by(order_direction + 'virus_test_3')
+    elif order_column == "virus_test_4":
+        result = subjects_to_be_ordered.order_by(order_direction + 'virus_test_4')
+    elif order_column == "virus_test_5":
+        result = subjects_to_be_ordered.order_by(order_direction + 'virus_test_5')
+    elif order_column == "virus_test_1_updated":
+        result = subjects_to_be_ordered.order_by(order_direction + 'virus_test_1_updated')
+    elif order_column == "virus_test_2_updated":
+        result = subjects_to_be_ordered.order_by(order_direction + 'virus_test_2_updated')
+    elif order_column == "virus_test_3_updated":
+        result = subjects_to_be_ordered.order_by(order_direction + 'virus_test_3_updated')
+    elif order_column == "virus_test_4_updated":
+        result = subjects_to_be_ordered.order_by(order_direction + 'virus_test_4_updated')
+    elif order_column == "virus_test_5_updated":
+        result = subjects_to_be_ordered.order_by(order_direction + 'virus_test_5_updated')
     else:
         logger.warn("Unknown sort column: " + str(order_column))
     return result
@@ -272,6 +306,31 @@ def get_subjects_filtered(subjects_to_be_filtered, filters):
             result = result.filter(resigned=(value == "true"))
         elif column == "endpoint_reached":
             result = result.filter(endpoint_reached=(value == "true"))
+        elif column == "virus_test_1":
+            if value == "null":
+                result = result.filter(virus_test_1__isnull=True)
+            else:
+                result = result.filter(virus_test_1=(value == "true"))
+        elif column == "virus_test_2":
+            if value == "null":
+                result = result.filter(virus_test_2__isnull=True)
+            else:
+                result = result.filter(virus_test_2=(value == "true"))
+        elif column == "virus_test_3":
+            if value == "null":
+                result = result.filter(virus_test_3__isnull=True)
+            else:
+                result = result.filter(virus_test_3=(value == "true"))
+        elif column == "virus_test_4":
+            if value == "null":
+                result = result.filter(virus_test_4__isnull=True)
+            else:
+                result = result.filter(virus_test_4=(value == "true"))
+        elif column == "virus_test_5":
+            if value == "null":
+                result = result.filter(virus_test_5__isnull=True)
+            else:
+                result = result.filter(virus_test_5=(value == "true"))
         elif column == "brain_donation_agreement":
             result = result.filter(brain_donation_agreement=(value == "true"))
         elif column == "postponed":
@@ -358,6 +417,7 @@ def types(request):
         "types": data
     })
 
+
 def serialize_subject(study_subject):
     location = location_to_str(study_subject.default_location)
     flying_team = flying_team_to_str(study_subject.flying_team)
@@ -388,7 +448,7 @@ def serialize_subject(study_subject):
                     status = "SHOULD_BE_IN_PROGRESS"
         else:
             status = "UPCOMING"
-        
+
         appointment_types = ['{} ({})'.format(at.code, at.description) for at in visit.appointment_types.all()]
         if len(appointment_types) == 0:
             appointment_types = ['No appointment types set.']
@@ -436,6 +496,16 @@ def serialize_subject(study_subject):
         "dead": bool_to_yes_no(study_subject.subject.dead),
         "resigned": bool_to_yes_no(study_subject.resigned),
         "endpoint_reached": bool_to_yes_no(study_subject.endpoint_reached),
+        "virus_test_1": bool_to_yes_no_null(study_subject.virus_test_1),
+        "virus_test_2": bool_to_yes_no_null(study_subject.virus_test_2),
+        "virus_test_3": bool_to_yes_no_null(study_subject.virus_test_3),
+        "virus_test_4": bool_to_yes_no_null(study_subject.virus_test_4),
+        "virus_test_5": bool_to_yes_no_null(study_subject.virus_test_5),
+        "virus_test_1_updated": study_subject.virus_test_1_updated,
+        "virus_test_2_updated": study_subject.virus_test_2_updated,
+        "virus_test_3_updated": study_subject.virus_test_3_updated,
+        "virus_test_4_updated": study_subject.virus_test_4_updated,
+        "virus_test_5_updated": study_subject.virus_test_5_updated,
         "postponed": bool_to_yes_no(study_subject.postponed),
         "brain_donation_agreement": bool_to_yes_no(study_subject.brain_donation_agreement),
         "excluded": bool_to_yes_no(study_subject.excluded),
@@ -447,4 +517,4 @@ def serialize_subject(study_subject):
         "id": study_subject.id,
         "visits": serialized_visits,
     }
-    return result
\ No newline at end of file
+    return result
diff --git a/smash/web/forms/study_subject_forms.py b/smash/web/forms/study_subject_forms.py
index 2bc880780226734cfa1653501960eb47bc8138b0..1f75992f6793598ef7d2c760af61af98ab90ab09 100644
--- a/smash/web/forms/study_subject_forms.py
+++ b/smash/web/forms/study_subject_forms.py
@@ -36,6 +36,17 @@ class StudySubjectAddForm(StudySubjectForm):
         self.study = get_study_from_args(kwargs)
 
         super(StudySubjectAddForm, self).__init__(*args, **kwargs)
+        self.fields['virus_test_1'].widget.attrs['readonly'] = True
+        self.fields['virus_test_2'].widget.attrs['readonly'] = True
+        self.fields['virus_test_3'].widget.attrs['readonly'] = True
+        self.fields['virus_test_4'].widget.attrs['readonly'] = True
+        self.fields['virus_test_5'].widget.attrs['readonly'] = True
+        self.fields['virus_test_1_updated'].widget.attrs['readonly'] = True
+        self.fields['virus_test_2_updated'].widget.attrs['readonly'] = True
+        self.fields['virus_test_3_updated'].widget.attrs['readonly'] = True
+        self.fields['virus_test_4_updated'].widget.attrs['readonly'] = True
+        self.fields['virus_test_5_updated'].widget.attrs['readonly'] = True
+
         prepare_study_subject_fields(fields=self.fields, study=self.study)
 
     def save(self, commit=True):
@@ -128,6 +139,18 @@ class StudySubjectEditForm(StudySubjectForm):
         self.fields['resigned'].disabled = was_resigned
         self.fields['endpoint_reached'].disabled = endpoint_was_reached
 
+
+        self.fields['virus_test_1'].widget.attrs['readonly'] = True
+        self.fields['virus_test_2'].widget.attrs['readonly'] = True
+        self.fields['virus_test_3'].widget.attrs['readonly'] = True
+        self.fields['virus_test_4'].widget.attrs['readonly'] = True
+        self.fields['virus_test_5'].widget.attrs['readonly'] = True
+        self.fields['virus_test_1_updated'].widget.attrs['readonly'] = True
+        self.fields['virus_test_2_updated'].widget.attrs['readonly'] = True
+        self.fields['virus_test_3_updated'].widget.attrs['readonly'] = True
+        self.fields['virus_test_4_updated'].widget.attrs['readonly'] = True
+        self.fields['virus_test_5_updated'].widget.attrs['readonly'] = True
+
         prepare_study_subject_fields(fields=self.fields, study=self.study)
 
     def clean(self):
@@ -181,6 +204,17 @@ def prepare_study_subject_fields(fields, study):
     prepare_field(fields, study.columns, 'previously_in_study')
     prepare_field(fields, study.columns, 'voucher_types')
 
+    prepare_field(fields, study.columns, 'virus_test_1')
+    prepare_field(fields, study.columns, 'virus_test_2')
+    prepare_field(fields, study.columns, 'virus_test_3')
+    prepare_field(fields, study.columns, 'virus_test_4')
+    prepare_field(fields, study.columns, 'virus_test_5')
+    prepare_field(fields, study.columns, 'virus_test_1_updated')
+    prepare_field(fields, study.columns, 'virus_test_2_updated')
+    prepare_field(fields, study.columns, 'virus_test_3_updated')
+    prepare_field(fields, study.columns, 'virus_test_4_updated')
+    prepare_field(fields, study.columns, 'virus_test_5_updated')
+
 
 def validate_subject_screening_number(self, cleaned_data):
     if self.study.columns.resign_reason:
diff --git a/smash/web/importer/csv_tns_visit_import_reader.py b/smash/web/importer/csv_tns_visit_import_reader.py
index 8bc4942a9639dff41e4885e24cd727543360720e..530696cb3f4c433f79ba2289496ee27065a5f77c 100644
--- a/smash/web/importer/csv_tns_visit_import_reader.py
+++ b/smash/web/importer/csv_tns_visit_import_reader.py
@@ -4,6 +4,7 @@ import datetime
 import logging
 import sys
 import traceback
+import codecs
 
 import pytz
 from django.conf import settings
@@ -38,7 +39,7 @@ class TnsCsvVisitImportReader:
 
         result = []
         with open(filename) as csv_file:
-            reader = csv.reader(csv_file, delimiter=';')
+            reader = csv.reader((remove_bom(line) for line in csv_file), delimiter=';')
             headers = next(reader, None)
             for row in reader:
                 try:
@@ -48,7 +49,7 @@ class TnsCsvVisitImportReader:
                     nd_number = data['donor_id']
                     study_subjects = StudySubject.objects.filter(nd_number=nd_number)
                     if len(study_subjects) == 0:
-                        logger.warn("Subject " + nd_number + " does not exist")
+                        logger.debug("Subject " + nd_number + " does not exist. Creating")
                         subject = Subject.objects.create()
                         study_subject = StudySubject.objects.create(subject=subject,
                                                                     study=Study.objects.filter(id=GLOBAL_STUDY_ID)[0],
@@ -77,7 +78,7 @@ class TnsCsvVisitImportReader:
                     visits = Visit.objects.filter(subject=study_subject, visit_number=visit_number)
 
                     if len(visits) > 0:
-                        logger.warn("Visit for subject " + nd_number + " already exists. Updating")
+                        logger.debug("Visit for subject " + nd_number + " already exists. Updating")
                         visit = visits[0]
                         visit.datetime_begin = date
                         visit.datetime_end = date + datetime.timedelta(days=14)
@@ -90,7 +91,7 @@ class TnsCsvVisitImportReader:
 
                     appointments = Appointment.objects.filter(visit=visit, appointment_types=self.appointment_type)
                     if len(appointments) > 0:
-                        logger.warn("Appointment for subject " + nd_number + " already set. Updating")
+                        logger.debug("Appointment for subject " + nd_number + " already set. Updating")
                         appointment = appointments[0]
                         appointment.length = 60
                         appointment.datetime_when = date
@@ -134,12 +135,75 @@ class TnsCsvVisitImportReader:
         text = data.get('adressofvisit', None)
         if text is None:
             text = data['lab_id']
-            if text.startswith('lab-reunis'):
-                text = u"Laboratoires réunis"
-            if text.startswith('lab-ketterthill'):
-                text = u"Ketterthill"
-            if text.startswith('lab-bionex'):
-                text = u"BioneXt"
+
+            if text.startswith('lab-reunis-1'):
+                text='Laboratoires réunis, 23 Route de Diekirch, 6555, Bollendorf-Pont'
+            if text.startswith('lab-reunis-2'):
+                text='Laboratoires réunis, 38 Rue Hiehl, 6131, Junglinster'
+            if text.startswith('lab-reunis-3'):
+                text='Laboratoires réunis, 16 Rue de la Gare, 6117, Junglinster'
+            if text.startswith('lab-reunis-4'):
+                text='Laboratoires réunis, 456 Rue de Neudorf, 2222, Luxembourg'
+            if text.startswith('lab-reunis-5'):
+                text='Laboratoires réunis, 14 Place St Michel, 7556, Mersch'
+            if text.startswith('lab-reunis-6'):
+                text='Laboratoires réunis, 2 Avenue des Bains, 5610, Mondorf-les-Bains'
+            if text.startswith('lab-reunis-7'):
+                text='Laboratoires réunis, 239 Route d\'Arlon, 8011, Strassen'
+            if text.startswith('lab-reunis-8'):
+                text='Laboratoires réunis, 123 Route de Diekirch, 7220, Walferdange'
+            if text.startswith('lab-reunis-9'):
+                text='Laboratoires réunis, 20 Rue de Luxembourg, 4220, Esch-sur-Alzette'
+            if text.startswith('lab-reunis-10'):
+                text='Laboratoires réunis, 124 Avenue de Luxembourg, 4940, Bascharage'
+            if text.startswith('lab-reunis-11'):
+                text='Laboratoires réunis, 1 Marbuergerstrooss, 9764, Marnach'
+            if text.startswith('lab-reunis-12'):
+                text='Laboratoires réunis, 51 Avenue Lucien Salentiny, 9080, Ettelbruck'
+            if text.startswith('lab-reunis-13'):
+                text='Laboratoires réunis, 14 route de l\'Europe, 5531, Remich'
+            if text.startswith('lab-reunis-14'):
+                text='Laboratoires réunis, 27, rue Principale, 5240, Sandweiler'
+            if text.startswith('lab-reunis-15'):
+                text='Laboratoires réunis, booking by phone 780 290-1, , '
+            if text.startswith('lab-bionext-16'):
+                text='BioneXt, 2 Rue du Chateau d\'Eau, 3364, Leudelange'
+            if text.startswith('lab-bionextpd-17'):
+                text='BioneXt, PickenDoheem'
+            if text.startswith('lab-ketterthill-18'):
+                text='Ketterthill, 11, rue Schwaarze Wee , 3474, Dudelange'
+            if text.startswith('lab-ketterthill-19'):
+                text='Ketterthill, 52, bd J.-F. Kennedy , 4170, Esch-sur-Alzette'
+            if text.startswith('lab-ketterthill-20'):
+                text='Ketterthill, 7, route de Bettembourg , 5810, Hesperange'
+            if text.startswith('lab-ketterthill-21'):
+                text='Ketterthill, Avenue des Bains (Dom. Thermal) , 5601, Mondorf-les-Bains'
+            if text.startswith('lab-ketterthill-22'):
+                text='Ketterthill, 8, avenue du Swing , 4367, Belvaux'
+            if text.startswith('lab-ketterthill-23'):
+                text='Ketterthill, 1-3, rue de la Continentale , 4917, Bascharage'
+            if text.startswith('lab-ketterthill-24'):
+                text='Ketterthill, 14, rue d\'Esch, 3920, Mondercange'
+            if text.startswith('lab-ketterthill-25'):
+                text='Ketterthill, 21, rue d\'Orval , 2270, Luxembourg'
+            if text.startswith('lab-ketterthill-26'):
+                text='Ketterthill, 24, rue Glesener , 1630, Luxembourg'
+            if text.startswith('lab-ketterthill-27'):
+                text='Ketterthill, 36, avenue Victor Hugo , 1750, Luxembourg'
+            if text.startswith('lab-ketterthill-28'):
+                text='Ketterthill, 70, rue de Luxembourg , 8140, Bridel'
+            if text.startswith('lab-ketterthill-29'):
+                text='Ketterthill, 15, rue Edward Steichen , 2540, Luxembourg'
+            if text.startswith('lab-ketterthill-30'):
+                text='Ketterthill, 29, rue Cents , 1319, Luxembourg'
+            if text.startswith('lab-ketterthill-31'):
+                text='Ketterthill, 155, rue Lucien Salentiny , 9080, Ettelbruck'
+            if text.startswith('lab-ketterthill-32'):
+                text='Ketterthill, 12, rue G.-D. Charlotte , 7520, Mersch'
+            if text.startswith('lab-ketterthill-33'):
+                text='Ketterthill, 18, rue de la Piscine , 8508, Redange-sur-Atert'
+            if text.startswith('lab-ketterthill-34'):
+                text='Ketterthill, 19, rue Grande-Duchesse Charlotte , 9515, Wiltz'
         locations = Location.objects.filter(name=text)
         if len(locations) > 0:
             return locations[0]
@@ -159,3 +223,6 @@ class TnsCsvVisitImportReader:
             style = ' color="brown" '
         result += "<p><font " + style + ">Number of raised warnings: <b>" + str(self.warning_count) + "</b></font></p>"
         return result
+
+def remove_bom(line):
+    return line[3:] if line.startswith(codecs.BOM_UTF8) else line
diff --git a/smash/web/migrations/0159_configurationitem_email_items_for_redcap.py b/smash/web/migrations/0159_configurationitem_email_items_for_redcap.py
new file mode 100644
index 0000000000000000000000000000000000000000..f496a5a3f92489be49b66ac9dbac156eb07e46db
--- /dev/null
+++ b/smash/web/migrations/0159_configurationitem_email_items_for_redcap.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-04-04 09:43
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+from web.models.constants import RED_CAP_LANGUAGE_4_FIELD_TYPE, RED_CAP_LANGUAGE_3_FIELD_TYPE, \
+    RED_CAP_LANGUAGE_2_FIELD_TYPE, RED_CAP_LANGUAGE_1_FIELD_TYPE, RED_CAP_MPOWER_ID_FIELD_TYPE, RED_CAP_DEAD_FIELD_TYPE, \
+    RED_CAP_SEX_FIELD_TYPE, RED_CAP_DATE_BORN_FIELD_TYPE, RED_CAP_ND_NUMBER_FIELD_TYPE, RED_CAP_VIRUS_FIELD_TYPE
+
+
+def create_item(apps, type, value, name):
+    # We can't import the ConfigurationItem model directly as it may be a newer
+    # version than this migration expects. We use the historical version.
+    ConfigurationItem = apps.get_model("web", "ConfigurationItem")
+    item = ConfigurationItem.objects.create()
+    item.type = type
+    item.value = value
+    item.name = name
+    item.save()
+
+
+def configuration_items(apps, schema_editor):
+    create_item(apps, RED_CAP_LANGUAGE_4_FIELD_TYPE, "dm_language_4",
+                "Redcap field for language 4")
+    create_item(apps, RED_CAP_LANGUAGE_3_FIELD_TYPE, "dm_language_3",
+                "Redcap field for language 3")
+    create_item(apps, RED_CAP_LANGUAGE_2_FIELD_TYPE, "dm_language_2",
+                "Redcap field for language 2")
+    create_item(apps, RED_CAP_LANGUAGE_1_FIELD_TYPE, "dm_language_1",
+                "Redcap field for language 1")
+    create_item(apps, RED_CAP_MPOWER_ID_FIELD_TYPE, "dm_mpowerid",
+                "Redcap field for mPowerId")
+    create_item(apps, RED_CAP_DEAD_FIELD_TYPE, "dm_death",
+                "Redcap field for deceased")
+    create_item(apps, RED_CAP_SEX_FIELD_TYPE, "cdisc_dm_sex",
+                "Redcap field for sex")
+    create_item(apps, RED_CAP_DATE_BORN_FIELD_TYPE, "cdisc_dm_brthdtc",
+                "Redcap field for birth date")
+    create_item(apps, RED_CAP_ND_NUMBER_FIELD_TYPE, "cdisc_dm_usubjd",
+                "Redcap field for subject id")
+    create_item(apps, RED_CAP_VIRUS_FIELD_TYPE, "",
+                "Redcap field for virus test result")
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('web', '0158_configurationitem_email_items'),
+    ]
+
+    operations = [
+        migrations.RunPython(configuration_items),
+    ]
diff --git a/smash/web/migrations/0160_auto_20200415_1101.py b/smash/web/migrations/0160_auto_20200415_1101.py
new file mode 100644
index 0000000000000000000000000000000000000000..f00f970dcc844171d9c43b7743823b5e4b2772bb
--- /dev/null
+++ b/smash/web/migrations/0160_auto_20200415_1101.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-04-15 11:01
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0159_configurationitem_email_items_for_redcap'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='studycolumns',
+            name='virus_test_1',
+            field=models.BooleanField(default=False, verbose_name=b'Visit 1 virus results'),
+        ),
+        migrations.AddField(
+            model_name='studycolumns',
+            name='virus_test_1_updated',
+            field=models.BooleanField(default=False, verbose_name=b'Visit 1 virus results date'),
+        ),
+        migrations.AddField(
+            model_name='studycolumns',
+            name='virus_test_2',
+            field=models.BooleanField(default=False, verbose_name=b'Visit 2 virus results'),
+        ),
+        migrations.AddField(
+            model_name='studycolumns',
+            name='virus_test_2_updated',
+            field=models.BooleanField(default=False, verbose_name=b'Visit 2 virus results date'),
+        ),
+        migrations.AddField(
+            model_name='studycolumns',
+            name='virus_test_3',
+            field=models.BooleanField(default=False, verbose_name=b'Visit 3 virus results'),
+        ),
+        migrations.AddField(
+            model_name='studycolumns',
+            name='virus_test_3_updated',
+            field=models.BooleanField(default=False, verbose_name=b'Visit 3 virus results date'),
+        ),
+        migrations.AddField(
+            model_name='studycolumns',
+            name='virus_test_4',
+            field=models.BooleanField(default=False, verbose_name=b'Visit 4 virus results'),
+        ),
+        migrations.AddField(
+            model_name='studycolumns',
+            name='virus_test_4_updated',
+            field=models.BooleanField(default=False, verbose_name=b'Visit 4 virus results date'),
+        ),
+        migrations.AddField(
+            model_name='studycolumns',
+            name='virus_test_5',
+            field=models.BooleanField(default=False, verbose_name=b'Visit 5 virus results'),
+        ),
+        migrations.AddField(
+            model_name='studycolumns',
+            name='virus_test_5_updated',
+            field=models.BooleanField(default=False, verbose_name=b'Visit 5 virus results date'),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_1',
+            field=models.NullBooleanField(choices=[(True, b'Yes'), (False, b'No'), (None, b'N/A')], default=None, editable=False, verbose_name=b'Visit 1 virus result'),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_1_updated',
+            field=models.DateField(editable=False, null=True, verbose_name=b'Visit 1 virus result date'),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_2',
+            field=models.NullBooleanField(choices=[(True, b'Yes'), (False, b'No'), (None, b'N/A')], default=None, editable=False, verbose_name=b'Visit 2 virus result'),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_2_updated',
+            field=models.DateField(editable=False, null=True, verbose_name=b'Visit 2 virus result date'),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_3',
+            field=models.NullBooleanField(choices=[(True, b'Yes'), (False, b'No'), (None, b'N/A')], default=None, editable=False, verbose_name=b'Visit 3 virus result'),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_3_updated',
+            field=models.DateField(editable=False, null=True, verbose_name=b'Visit 3 virus result date'),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_4',
+            field=models.NullBooleanField(choices=[(True, b'Yes'), (False, b'No'), (None, b'N/A')], default=None, editable=False, verbose_name=b'Visit 4 virus result'),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_4_updated',
+            field=models.DateField(editable=False, null=True, verbose_name=b'Visit 4 virus result date'),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_5',
+            field=models.NullBooleanField(choices=[(True, b'Yes'), (False, b'No'), (None, b'N/A')], default=None, editable=False, verbose_name=b'Visit 5 virus result'),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_5_updated',
+            field=models.DateField(editable=False, null=True, verbose_name=b'Visit 5 virus result date'),
+        ),
+    ]
diff --git a/smash/web/migrations/0161_auto_20200416_0736.py b/smash/web/migrations/0161_auto_20200416_0736.py
new file mode 100644
index 0000000000000000000000000000000000000000..babe517464ca8315c824671d5c8d625bce3ec907
--- /dev/null
+++ b/smash/web/migrations/0161_auto_20200416_0736.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-04-16 07:36
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0160_auto_20200415_1101'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='study',
+            name='sample_mail_statistics',
+            field=models.BooleanField(default=False, verbose_name=b'Email with sample collections should use statistics'),
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='virus_test_1',
+            field=models.NullBooleanField(choices=[(True, b'Yes'), (False, b'No'), (None, b'N/A')], default=None, verbose_name=b'Visit 1 virus result'),
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='virus_test_1_updated',
+            field=models.DateField(blank=True, null=True, verbose_name=b'Visit 1 virus result date'),
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='virus_test_2',
+            field=models.NullBooleanField(choices=[(True, b'Yes'), (False, b'No'), (None, b'N/A')], default=None, verbose_name=b'Visit 2 virus result'),
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='virus_test_2_updated',
+            field=models.DateField(blank=True, null=True, verbose_name=b'Visit 2 virus result date'),
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='virus_test_3',
+            field=models.NullBooleanField(choices=[(True, b'Yes'), (False, b'No'), (None, b'N/A')], default=None, verbose_name=b'Visit 3 virus result'),
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='virus_test_3_updated',
+            field=models.DateField(blank=True, null=True, verbose_name=b'Visit 3 virus result date'),
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='virus_test_4',
+            field=models.NullBooleanField(choices=[(True, b'Yes'), (False, b'No'), (None, b'N/A')], default=None, verbose_name=b'Visit 4 virus result'),
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='virus_test_4_updated',
+            field=models.DateField(blank=True, null=True, verbose_name=b'Visit 4 virus result date'),
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='virus_test_5',
+            field=models.NullBooleanField(choices=[(True, b'Yes'), (False, b'No'), (None, b'N/A')], default=None, verbose_name=b'Visit 5 virus result'),
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='virus_test_5_updated',
+            field=models.DateField(blank=True, null=True, verbose_name=b'Visit 5 virus result date'),
+        ),
+    ]
diff --git a/smash/web/migrations/0162_auto_20200416_1212.py b/smash/web/migrations/0162_auto_20200416_1212.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e2bf7c91f719ca93400c4e47b5b206036833394
--- /dev/null
+++ b/smash/web/migrations/0162_auto_20200416_1212.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-04-16 12:12
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0161_auto_20200416_0736'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='configurationitem',
+            name='value',
+            field=models.CharField(max_length=1024, verbose_name=b'Value'),
+        ),
+        migrations.AlterField(
+            model_name='subject',
+            name='next_of_keen_name',
+            field=models.CharField(blank=True, max_length=255, verbose_name=b'Next of keen'),
+        ),
+    ]
diff --git a/smash/web/migrations/0163_study_redcap_first_visit_number.py b/smash/web/migrations/0163_study_redcap_first_visit_number.py
new file mode 100644
index 0000000000000000000000000000000000000000..e518d8ef98accd9b26caad3e036d12ee6147c0cb
--- /dev/null
+++ b/smash/web/migrations/0163_study_redcap_first_visit_number.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-04-16 12:36
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0162_auto_20200416_1212'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='study',
+            name='redcap_first_visit_number',
+            field=models.IntegerField(default=1, verbose_name=b'Number of the first visit in redcap system'),
+        ),
+    ]
diff --git a/smash/web/models/configuration_item.py b/smash/web/models/configuration_item.py
index ed4b3e39faa17a8f0d5ea231cc178fd2a3fe4ee9..86e3d12c5649c0727bff560d8b894c42152e2b33 100644
--- a/smash/web/models/configuration_item.py
+++ b/smash/web/models/configuration_item.py
@@ -21,7 +21,7 @@ class ConfigurationItem(models.Model):
                             editable=False
                             )
 
-    value = models.CharField(max_length=50,
+    value = models.CharField(max_length=1024,
                              verbose_name='Value',
                              )
 
diff --git a/smash/web/models/constants.py b/smash/web/models/constants.py
index e83ddc83fa6dc4ea020850952d9fb151f97ab3d4..b512e714efac9136e8c4d9d9bc048f77d065458f 100644
--- a/smash/web/models/constants.py
+++ b/smash/web/models/constants.py
@@ -10,6 +10,11 @@ SEX_CHOICES = (
     (SEX_CHOICES_MALE, 'Male'),
     (SEX_CHOICES_FEMALE, 'Female'),
 )
+BOOL_CHOICES_WITH_NONE = (
+    (True, 'Yes'),
+    (False, 'No'),
+    (None, 'N/A'),
+)
 SUBJECT_TYPE_CHOICES_CONTROL = 'C'
 SUBJECT_TYPE_CHOICES_PATIENT = 'P'
 SUBJECT_TYPE_CHOICES = {
@@ -47,6 +52,17 @@ KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE = "KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_
 KIT_DAILY_EMAIL_DAYS_PERIOD_TYPE = "KIT_DAILY_EMAIL_DAYS_PERIOD_TYPE"
 KIT_DAILY_EMAIL_TIME_FORMAT_TYPE = "KIT_DAILY_EMAIL_TIME_FORMAT_TYPE"
 
+RED_CAP_LANGUAGE_4_FIELD_TYPE = 'RED_CAP_LANGUAGE_4_FIELD_TYPE'
+RED_CAP_LANGUAGE_3_FIELD_TYPE = 'RED_CAP_LANGUAGE_3_FIELD_TYPE'
+RED_CAP_LANGUAGE_2_FIELD_TYPE = 'RED_CAP_LANGUAGE_2_FIELD_TYPE'
+RED_CAP_LANGUAGE_1_FIELD_TYPE = 'RED_CAP_LANGUAGE_1_FIELD_TYPE'
+RED_CAP_MPOWER_ID_FIELD_TYPE = 'RED_CAP_MPOWER_ID_FIELD_TYPE'
+RED_CAP_DEAD_FIELD_TYPE = 'RED_CAP_DEAD_FIELD_TYPE'
+RED_CAP_SEX_FIELD_TYPE = 'RED_CAP_SEX_FIELD_TYPE'
+RED_CAP_DATE_BORN_FIELD_TYPE = 'RED_CAP_DATE_BORN_FIELD_TYPE'
+RED_CAP_ND_NUMBER_FIELD_TYPE = 'RED_CAP_ND_NUMBER_FIELD_TYPE'
+RED_CAP_VIRUS_FIELD_TYPE = 'RED_CAP_VIRUS_FIELD_TYPE'
+
 MAIL_TEMPLATE_CONTEXT_SUBJECT = 'S'
 MAIL_TEMPLATE_CONTEXT_APPOINTMENT = 'A'
 MAIL_TEMPLATE_CONTEXT_VISIT = 'V'
diff --git a/smash/web/models/study.py b/smash/web/models/study.py
index b3baf2f706b7d6191228272d8c61e7be9bb6eb48..ed1fa433b140ad626c5746f5440a1ee6143b5ef0 100644
--- a/smash/web/models/study.py
+++ b/smash/web/models/study.py
@@ -43,6 +43,16 @@ class Study(models.Model):
         verbose_name="Auto create follow up visit"
     )
 
+    redcap_first_visit_number = models.IntegerField(
+        default=1,
+        verbose_name="Number of the first visit in redcap system"
+    )
+
+    sample_mail_statistics = models.BooleanField(
+        default=False,
+        verbose_name="Email with sample collections should use statistics"
+    )
+
     visits_to_show_in_subject_list = models.IntegerField(
         verbose_name='Number of visits to show in the subject list',
         default=5,
diff --git a/smash/web/models/study_columns.py b/smash/web/models/study_columns.py
index 7d20b3913d40a71f72fc0b70327a20084e83a158..d2e769ca52216cf9560933ec89d9902f3092220e 100644
--- a/smash/web/models/study_columns.py
+++ b/smash/web/models/study_columns.py
@@ -12,69 +12,69 @@ class StudyColumns(models.Model):
     )
 
     datetime_contact_reminder = models.BooleanField(
-                                                    default=True,
-                                                    verbose_name='Please make a contact on'
-                                                    )
+        default=True,
+        verbose_name='Please make a contact on'
+    )
     type = models.BooleanField(
-                               default=True,
-                               verbose_name='Type'
-                               )
+        default=True,
+        verbose_name='Type'
+    )
 
     default_location = models.BooleanField(
-                                           default=True,
-                                           verbose_name='Default appointment location',
-                                           )
+        default=True,
+        verbose_name='Default appointment location',
+    )
 
     flying_team = models.BooleanField(
-                                      default=True,
-                                      verbose_name='Default flying team location (if applicable)',
-                                      )
+        default=True,
+        verbose_name='Default flying team location (if applicable)',
+    )
 
     screening_number = models.BooleanField(
-                                           default=True,
-                                           verbose_name='Screening number',
-                                           )
+        default=True,
+        verbose_name='Screening number',
+    )
     nd_number = models.BooleanField(
-                                    default=True,
-                                    verbose_name='ND number',
-                                    )
+        default=True,
+        verbose_name='ND number',
+    )
     mpower_id = models.BooleanField(
-                                    default=True,
-                                    verbose_name='MPower ID'
-                                    )
+        default=True,
+        verbose_name='MPower ID'
+    )
     comments = models.BooleanField(
-                                   default=True,
-                                   verbose_name='Comments'
-                                   )
+        default=True,
+        verbose_name='Comments'
+    )
     referral = models.BooleanField(
-                                   default=True,
-                                   verbose_name='Referred by'
-                                   )
+        default=True,
+        verbose_name='Referred by'
+    )
     diagnosis = models.BooleanField(
-                                    default=True,
-                                    verbose_name='Diagnosis'
-                                    )
+        default=True,
+        verbose_name='Diagnosis'
+    )
     year_of_diagnosis = models.BooleanField(
-                                            default=True,
-                                            verbose_name='Year of diagnosis (YYYY)'
-                                            )
+        default=True,
+        verbose_name='Year of diagnosis (YYYY)'
+    )
 
     information_sent = models.BooleanField(
-                                           default=True,
-                                           verbose_name='Information sent',
-                                           )
+        default=True,
+        verbose_name='Information sent',
+    )
     pd_in_family = models.BooleanField(
-                                       default=True,
-                                       verbose_name='PD in family',
-                                       )
+        default=True,
+        verbose_name='PD in family',
+    )
     resigned = models.BooleanField(
-                                   default=True,
-                                   verbose_name='Resigned',
-                                   )
+        default=True,
+        verbose_name='Resigned',
+    )
     resign_reason = models.BooleanField(
-                                        default=True,
-                                        verbose_name='Resign reason'
-                                        )
+        default=True,
+        verbose_name='Resign reason'
+    )
 
     excluded = models.BooleanField(default=False, verbose_name='Excluded')
 
@@ -83,41 +83,82 @@ class StudyColumns(models.Model):
     resign_reason = models.BooleanField(default=True, verbose_name='Endpoint reached comments')
 
     referral_letter = models.BooleanField(
-                                          default=False,
-                                          verbose_name='Referral letter'
-                                          )
+        default=False,
+        verbose_name='Referral letter'
+    )
 
     health_partner = models.BooleanField(
-                                         default=False,
-                                         verbose_name='Health partner'
-                                         )
+        default=False,
+        verbose_name='Health partner'
+    )
 
     health_partner_feedback_agreement = models.BooleanField(
-                                                            default=False,
-                                                            verbose_name='Agrees to give information to referral'
-                                                            )
+        default=False,
+        verbose_name='Agrees to give information to referral'
+    )
 
     screening = models.BooleanField(
-                                    default=False,
-                                    verbose_name='Screening'
-                                    )
+        default=False,
+        verbose_name='Screening'
+    )
 
     previously_in_study = models.BooleanField(
-                                              default=False,
-                                              verbose_name='Previously in PDP study',
-                                              )
+        default=False,
+        verbose_name='Previously in PDP study',
+    )
 
     voucher_types = models.BooleanField(
-                                        default=False,
-                                        verbose_name='Voucher types',
-                                        )
+        default=False,
+        verbose_name='Voucher types',
+    )
 
     vouchers = models.BooleanField(
-                                   default=False,
-                                   verbose_name='Vouchers',
-                                   )
+        default=False,
+        verbose_name='Vouchers',
+    )
 
     brain_donation_agreement = models.BooleanField(
-                                                   default=False,
-                                                   verbose_name='Brain donation agreement',
-                                                   )
+        default=False,
+        verbose_name='Brain donation agreement',
+    )
+
+    virus_test_1 = models.BooleanField(
+        default=False,
+        verbose_name='Visit 1 virus results',
+    )
+    virus_test_2 = models.BooleanField(
+        default=False,
+        verbose_name='Visit 2 virus results',
+    )
+    virus_test_3 = models.BooleanField(
+        default=False,
+        verbose_name='Visit 3 virus results',
+    )
+    virus_test_4 = models.BooleanField(
+        default=False,
+        verbose_name='Visit 4 virus results',
+    )
+    virus_test_5 = models.BooleanField(
+        default=False,
+        verbose_name='Visit 5 virus results',
+    )
+    virus_test_1_updated = models.BooleanField(
+        default=False,
+        verbose_name='Visit 1 virus results date',
+    )
+    virus_test_2_updated = models.BooleanField(
+        default=False,
+        verbose_name='Visit 2 virus results date',
+    )
+    virus_test_3_updated = models.BooleanField(
+        default=False,
+        verbose_name='Visit 3 virus results date',
+    )
+    virus_test_4_updated = models.BooleanField(
+        default=False,
+        verbose_name='Visit 4 virus results date',
+    )
+    virus_test_5_updated = models.BooleanField(
+        default=False,
+        verbose_name='Visit 5 virus results date',
+    )
diff --git a/smash/web/models/study_subject.py b/smash/web/models/study_subject.py
index 22a5b0b8fe77518abb0092587531f0b715413633..d802d8debb0e5d87dc0a54756bc9152349855cd9 100644
--- a/smash/web/models/study_subject.py
+++ b/smash/web/models/study_subject.py
@@ -1,23 +1,25 @@
 # coding=utf-8
 import logging
 import re
-from django.db import models
 
-from web.models import VoucherType, Appointment, Location, Visit, Provenance
-from web.models.constants import BOOL_CHOICES, SUBJECT_TYPE_CHOICES, FILE_STORAGE
+from django.db import models
 from django.db.models.signals import post_save
 from django.dispatch import receiver
 
+from web.models import VoucherType, Appointment, Location, Visit, Provenance
+from web.models.constants import BOOL_CHOICES, SUBJECT_TYPE_CHOICES, FILE_STORAGE, BOOL_CHOICES_WITH_NONE
+
 logger = logging.getLogger(__name__)
 
-class StudySubject(models.Model):
 
+class StudySubject(models.Model):
     class Meta:
         app_label = 'web'
 
     @property
     def provenances(self):
-      return Provenance.objects.filter(modified_table=StudySubject._meta.db_table, modified_table_id=self.id).order_by('-modification_date')
+        return Provenance.objects.filter(modified_table=StudySubject._meta.db_table,
+                                         modified_table_id=self.id).order_by('-modification_date')
 
     def finish_all_visits(self):
         visits = Visit.objects.filter(subject=self, is_finished=False)
@@ -168,9 +170,9 @@ class StudySubject(models.Model):
         default=None,
     )
     brain_donation_agreement = models.BooleanField(
-                                                   default=False,
-                                                   verbose_name='Brain donation agreement',
-                                                   )
+        default=False,
+        verbose_name='Brain donation agreement',
+    )
 
     resigned = models.BooleanField(
         verbose_name='Resigned',
@@ -196,42 +198,84 @@ class StudySubject(models.Model):
         editable=True
     )
     endpoint_reached_reason = models.TextField(max_length=2000,
-                                      blank=True,
-                                      verbose_name='Endpoint reached comments'
-                                      )
+                                               blank=True,
+                                               verbose_name='Endpoint reached comments'
+                                               )
+
+    virus_test_1 = models.NullBooleanField(choices=BOOL_CHOICES_WITH_NONE,
+                                           verbose_name='Visit 1 virus result',
+                                           default=None
+                                           )
+    virus_test_2 = models.NullBooleanField(choices=BOOL_CHOICES_WITH_NONE,
+                                           verbose_name='Visit 2 virus result',
+                                           default=None
+                                           )
+    virus_test_3 = models.NullBooleanField(choices=BOOL_CHOICES_WITH_NONE,
+                                           verbose_name='Visit 3 virus result',
+                                           default=None
+                                           )
+    virus_test_4 = models.NullBooleanField(choices=BOOL_CHOICES_WITH_NONE,
+                                           verbose_name='Visit 4 virus result',
+                                           default=None
+                                           )
+    virus_test_5 = models.NullBooleanField(choices=BOOL_CHOICES_WITH_NONE,
+                                           verbose_name='Visit 5 virus result',
+                                           default=None
+                                           )
+    virus_test_1_updated = models.DateField(verbose_name='Visit 1 virus result date',
+                                            blank=True,
+                                            null=True,
+                                            )
+    virus_test_2_updated = models.DateField(verbose_name='Visit 2 virus result date',
+                                            blank=True,
+                                            null=True,
+                                            )
+    virus_test_3_updated = models.DateField(verbose_name='Visit 3 virus result date',
+                                            blank=True,
+                                            null=True,
+                                            )
+    virus_test_4_updated = models.DateField(verbose_name='Visit 4 virus result date',
+                                            blank=True,
+                                            null=True,
+                                            )
+    virus_test_5_updated = models.DateField(verbose_name='Visit 5 virus result date',
+                                            blank=True,
+                                            null=True,
+                                            )
 
     def sort_matched_screening_first(self, pattern, reverse=False):
-      if self.screening_number is None:
-        return None
-
-      parts = self.screening_number.split(';')
-      matches, reminder = [], []
-  
-      try:
-        for part in parts:
-            chunks = part.strip().split('-')
-            if len(chunks) == 2:
-                letter, number = chunks
-                try:
-                  tupl = (letter, int(number))
-                except ValueError: #better than isdigit because isdigit fails with negative numbers and others
-                  tupl = (letter, number)
-            else:
-                logger.warn('There are {} chunks in some parts of this screening_number: |{}|.'.format(
-                    len(chunks), self.screening_number))
-                tupl = (part.strip(), None)
-            if pattern is not None and pattern in part:
-                matches.append(tupl)
-            else:
-                reminder.append(tupl)
-      except: #if the format is not the expected format
-        matches = parts
-
-      return matches + sorted(reminder, reverse=reverse)
+        if self.screening_number is None:
+            return None
+
+        parts = self.screening_number.split(';')
+        matches, reminder = [], []
+
+        try:
+            for part in parts:
+                chunks = part.strip().split('-')
+                if len(chunks) == 2:
+                    letter, number = chunks
+                    try:
+                        tupl = (letter, int(number))
+                    except ValueError:  # better than isdigit because isdigit fails with negative numbers and others
+                        tupl = (letter, number)
+                else:
+                    logger.warn('There are {} chunks in some parts of this screening_number: |{}|.'.format(
+                        len(chunks), self.screening_number))
+                    tupl = (part.strip(), None)
+                if pattern is not None and pattern in part:
+                    matches.append(tupl)
+                else:
+                    reminder.append(tupl)
+        except:  # if the format is not the expected format
+            matches = parts
+
+        return matches + sorted(reminder, reverse=reverse)
 
     @staticmethod
     def check_nd_number_regex(regex_str, study):
-        nd_numbers = StudySubject.objects.filter(study=study).exclude(nd_number__isnull=True).exclude(nd_number__exact='').all().values_list('nd_number', flat=True)
+        nd_numbers = StudySubject.objects.filter(study=study).exclude(nd_number__isnull=True).exclude(
+            nd_number__exact='').all().values_list('nd_number', flat=True)
         regex = re.compile(regex_str)
         for nd_number in nd_numbers:
             if regex.match(nd_number) is None:
@@ -239,21 +283,20 @@ class StudySubject(models.Model):
         return True
 
     def can_schedule(self):
-      return not any([self.resigned, self.excluded, self.endpoint_reached, self.subject.dead])
+        return not any([self.resigned, self.excluded, self.endpoint_reached, self.subject.dead])
 
     @property
     def status(self):
-      if self.subject.dead:
-        return 'Deceased'
-      elif self.resigned:
-        return 'Resigned'
-      elif self.excluded:
-        return 'Excluded'
-      elif self.endpoint_reached:
-        return 'Endpoint Reached'
-      else:
-        return 'Normal'
-    
+        if self.subject.dead:
+            return 'Deceased'
+        elif self.resigned:
+            return 'Resigned'
+        elif self.excluded:
+            return 'Excluded'
+        elif self.endpoint_reached:
+            return 'Endpoint Reached'
+        else:
+            return 'Normal'
 
     def __str__(self):
         return "%s %s" % (self.subject.first_name, self.subject.last_name)
@@ -261,12 +304,13 @@ class StudySubject(models.Model):
     def __unicode__(self):
         return "%s %s" % (self.subject.first_name, self.subject.last_name)
 
-#SIGNALS
+
+# SIGNALS
 @receiver(post_save, sender=StudySubject)
 def set_as_resigned_or_excluded_or_endpoint_reached(sender, instance, **kwargs):
-  if instance.excluded:
-    instance.mark_as_excluded()
-  if instance.resigned:
-    instance.mark_as_resigned()
-  if instance.endpoint_reached:
-    instance.mark_as_endpoint_reached()
\ No newline at end of file
+    if instance.excluded:
+        instance.mark_as_excluded()
+    if instance.resigned:
+        instance.mark_as_resigned()
+    if instance.endpoint_reached:
+        instance.mark_as_endpoint_reached()
diff --git a/smash/web/models/subject.py b/smash/web/models/subject.py
index 918d4645513b2a690bb163d79c8b448b741dc141..0f7c3c35441f692e44b17f8df61202b742b69356 100644
--- a/smash/web/models/subject.py
+++ b/smash/web/models/subject.py
@@ -105,7 +105,7 @@ class Subject(models.Model):
                                 verbose_name='Country'
                                 )
 
-    next_of_keen_name = models.CharField(max_length=50,
+    next_of_keen_name = models.CharField(max_length=255,
                                          blank=True,
                                          verbose_name='Next of keen'
                                          )
diff --git a/smash/web/redcap_connector.py b/smash/web/redcap_connector.py
index 4e1da4fa683708eb0a6c28dc46c7ae214622f143..8329ef604bee1fbbbf48e75bd29648c149ffb350 100644
--- a/smash/web/redcap_connector.py
+++ b/smash/web/redcap_connector.py
@@ -1,43 +1,25 @@
 # coding=utf-8
 import cStringIO
+import datetime
 import json
 import logging
-import pycurl
 
 import certifi
+import pycurl
 import timeout_decorator
+from django.conf import settings
+from django.forms.models import model_to_dict
 from django_cron import CronJobBase, Schedule
 
-from django.forms.models import model_to_dict
-from web.models.constants import GLOBAL_STUDY_ID
-from web.models import ConfigurationItem, StudySubject, Language, Study, StudyRedCapColumns
+from web.models import ConfigurationItem, StudySubject, Language, AppointmentType, Appointment, Visit, Study
 from web.models.constants import REDCAP_TOKEN_CONFIGURATION_TYPE, \
-    REDCAP_BASE_URL_CONFIGURATION_TYPE, CRON_JOB_TIMEOUT
+    REDCAP_BASE_URL_CONFIGURATION_TYPE, CRON_JOB_TIMEOUT, RED_CAP_LANGUAGE_4_FIELD_TYPE, RED_CAP_LANGUAGE_3_FIELD_TYPE, \
+    RED_CAP_LANGUAGE_2_FIELD_TYPE, RED_CAP_LANGUAGE_1_FIELD_TYPE, RED_CAP_MPOWER_ID_FIELD_TYPE, RED_CAP_DEAD_FIELD_TYPE, \
+    RED_CAP_SEX_FIELD_TYPE, RED_CAP_DATE_BORN_FIELD_TYPE, RED_CAP_ND_NUMBER_FIELD_TYPE, RED_CAP_VIRUS_FIELD_TYPE, \
+    GLOBAL_STUDY_ID
 from web.models.inconsistent_subject import InconsistentField, InconsistentSubject
 from web.models.missing_subject import MissingSubject
 
-RED_CAP_LANGUAGE_4_FIELD = 'dm_language_4'
-
-RED_CAP_LANGUAGE_3_FIELD = 'dm_language_3'
-
-RED_CAP_LANGUAGE_2_FIELD = 'dm_language_2'
-
-RED_CAP_LANGUAGE_1_FIELD = 'dm_language_1'
-
-# noinspection SpellCheckingInspection
-RED_CAP_MPOWER_ID_FIELD = 'dm_mpowerid'
-
-RED_CAP_DEAD_FIELD = 'dm_death'
-
-# noinspection SpellCheckingInspection
-RED_CAP_SEX_FIELD = 'cdisc_dm_sex'
-
-# noinspection SpellCheckingInspection
-RED_CAP_DATE_BORN_FIELD = 'cdisc_dm_brthdtc'
-
-# noinspection SpellCheckingInspection
-RED_CAP_ND_NUMBER_FIELD = 'cdisc_dm_usubjd'
-
 logger = logging.getLogger(__name__)
 
 
@@ -52,12 +34,18 @@ class RedcapSubject(object):
 
     def __init__(self):
         self.languages = []
+        self.visits = []
 
     def add_language(self, language):
         if language is not None:
             self.languages.append(language)
 
 
+class RedcapVisit(object):
+    virus = None
+    visit_number = 0
+
+
 def different_string(string1, string2):
     if string1 is None:
         string1 = ""
@@ -83,6 +71,19 @@ class RedcapConnector(object):
         for language in languages:
             self.language_by_name[language.name.lower()] = language
 
+        self.date_born_field = ConfigurationItem.objects.get(type=RED_CAP_DATE_BORN_FIELD_TYPE).value
+        self.sex_field = ConfigurationItem.objects.get(type=RED_CAP_SEX_FIELD_TYPE).value
+        self.nd_number_field = ConfigurationItem.objects.get(type=RED_CAP_ND_NUMBER_FIELD_TYPE).value
+        self.dead_field = ConfigurationItem.objects.get(type=RED_CAP_DEAD_FIELD_TYPE).value
+        self.language_1_field = ConfigurationItem.objects.get(type=RED_CAP_LANGUAGE_1_FIELD_TYPE).value
+        self.language_2_field = ConfigurationItem.objects.get(type=RED_CAP_LANGUAGE_2_FIELD_TYPE).value
+        self.language_3_field = ConfigurationItem.objects.get(type=RED_CAP_LANGUAGE_3_FIELD_TYPE).value
+        self.language_4_field = ConfigurationItem.objects.get(type=RED_CAP_LANGUAGE_4_FIELD_TYPE).value
+        self.m_power_id_field = ConfigurationItem.objects.get(type=RED_CAP_MPOWER_ID_FIELD_TYPE).value
+        self.virus_field = ConfigurationItem.objects.get(type=RED_CAP_VIRUS_FIELD_TYPE).value
+
+        self.study = Study.objects.get(id=GLOBAL_STUDY_ID)
+
     def find_missing(self):
         pid = self.get_project_id()
         redcap_version = self.get_redcap_version()
@@ -157,6 +158,13 @@ class RedcapConnector(object):
         self.add_inconsistent(inconsistent)
 
     def find_inconsistent(self):
+        appointment_type_code_to_finish = getattr(settings, "IMPORT_APPOINTMENT_TYPE", None)
+        appointment_type_to_finish = None
+        if appointment_type_code_to_finish is not None:
+            appointment_types = AppointmentType.objects.filter(code=appointment_type_code_to_finish)
+            if len(appointment_types) > 0:
+                appointment_type_to_finish = appointment_types[0]
+
         pid = self.get_project_id()
         redcap_version = self.get_redcap_version()
 
@@ -173,9 +181,41 @@ class RedcapConnector(object):
 
             if red_cap_subject is not None:
                 url = self.create_redcap_link(pid, redcap_version, subject)
-                subject = self.create_inconsistency_subject(red_cap_subject, subject, url)
-                if subject is not None:
-                    result.append(subject)
+
+                inconsistent_subject = self.create_inconsistency_subject(red_cap_subject, subject, url)
+                if inconsistent_subject is not None:
+                    result.append(inconsistent_subject)
+                if appointment_type_to_finish is not None:
+                    for visit in red_cap_subject.visits:
+                        smasch_visits = Visit.objects.filter(visit_number=visit.visit_number, subject=subject)
+                        smasch_appointments = Appointment.objects.filter(visit__in=smasch_visits,
+                                                                         appointment_types=appointment_type_to_finish,
+                                                                         status=Appointment.APPOINTMENT_STATUS_SCHEDULED)
+                        for smasch_appointment in smasch_appointments:
+                            smasch_appointment.mark_as_finished()
+                            smasch_appointment.visit.is_finished = True
+                            smasch_appointment.visit.save()
+                        if visit.virus is not None:
+                            if visit.visit_number == 1 and subject.virus_test_1 != visit.virus:
+                                subject.virus_test_1 = visit.virus
+                                subject.virus_test_1_updated = datetime.datetime.now()
+                                subject.save()
+                            if visit.visit_number == 2 and subject.virus_test_2 != visit.virus:
+                                subject.virus_test_2 = visit.virus
+                                subject.virus_test_2_updated = datetime.datetime.now()
+                                subject.save()
+                            if visit.visit_number == 3 and subject.virus_test_3 != visit.virus:
+                                subject.virus_test_3 = visit.virus
+                                subject.virus_test_3_updated = datetime.datetime.now()
+                                subject.save()
+                            if visit.visit_number == 4 and subject.virus_test_4 != visit.virus:
+                                subject.virus_test_4 = visit.virus
+                                subject.virus_test_4_updated = datetime.datetime.now()
+                                subject.save()
+                            if visit.visit_number == 5 and subject.virus_test_5 != visit.virus:
+                                subject.virus_test_5 = visit.virus
+                                subject.virus_test_5_updated = datetime.datetime.now()
+                                subject.save()
 
         return result
 
@@ -228,7 +268,7 @@ class RedcapConnector(object):
 
     @staticmethod
     def create_inconsistency_subject(red_cap_subject, study_subject, url):
-        #func dict
+        # func dict
         field_checks = {
             'sex': RedcapConnector.check_sex_consistency,
             'date_born': RedcapConnector.check_birth_date_consistency,
@@ -239,14 +279,14 @@ class RedcapConnector(object):
 
         fields = []
 
-        #get fields which are true from redcap columns
-        fields_to_check = [k for k,v in model_to_dict(study_subject.study.redcap_columns).iteritems() if v is True]
+        # get fields which are true from redcap columns
+        fields_to_check = [k for k, v in model_to_dict(study_subject.study.redcap_columns).iteritems() if v is True]
 
         for field_to_check in fields_to_check:
             field = field_checks[field_to_check](red_cap_subject, study_subject)
             if field is not None:
                 fields.append(field)
-        
+
         result = None
         if len(fields) > 0:
             result = InconsistentSubject.create(smash_subject=study_subject, url=url, fields=fields)
@@ -257,21 +297,68 @@ class RedcapConnector(object):
                subject.nd_number + "&page=demographics"
 
     def get_red_cap_subjects(self):
-        query_data = {
+
+        query_data = self.get_subject_query_data()
+        data = self.execute_query(query_data)
+        result = []
+        for row in data:
+            if isinstance(row, dict):
+                redcap_subject = RedcapSubject()
+                redcap_subject.nd_number = row.get(self.nd_number_field)
+                if self.date_born_field != "":
+                    redcap_subject.date_born = row.get(self.date_born_field)
+                if self.sex_field != "":
+                    redcap_subject.sex = row.get(self.sex_field)
+                if self.dead_field != "":
+                    redcap_subject.dead = (row.get(self.dead_field).lower() == "yes")
+                if self.m_power_id_field != "":
+                    redcap_subject.mpower_id = row.get(self.m_power_id_field)
+                if self.language_1_field != "" and row.get(self.language_1_field):
+                    redcap_subject.add_language(self.get_language(row.get(self.language_1_field)))
+                if self.language_2_field != "" and row[self.language_2_field]:
+                    redcap_subject.add_language(self.get_language(row.get(self.language_2_field)))
+                if self.language_3_field != "" and row[self.language_3_field]:
+                    redcap_subject.add_language(self.get_language(row.get(self.language_3_field)))
+                if self.language_4_field != "" and row[self.language_4_field]:
+                    redcap_subject.add_language(self.get_language(row.get(self.language_4_field)))
+                visit = RedcapVisit()
+                visit.visit_number = 1
+                if self.virus_field != "":
+                    if row.get(self.virus_field) == "Negative":
+                        visit.virus = False
+                    elif row.get(self.virus_field) == "Positive":
+                        visit.virus = True
+                redcap_subject.visits.append(visit)
+                result.append(redcap_subject)
+        for i in range(1, 9):
+            query_data = self.get_subject_query_data()
+            query_data["events[0]"] = "visit_" + str(i + self.study.redcap_first_visit_number) + "_arm_1"
+            data = self.execute_query(query_data)
+            if isinstance(data, dict):
+                break
+            for row in data:
+                if isinstance(row, dict):
+                    nd_number = row.get(self.nd_number_field)
+                    for redcap_subject in result:
+                        if redcap_subject.nd_number == nd_number:
+                            visit = RedcapVisit()
+                            visit.visit_number = i
+                            if self.virus_field != "":
+                                if row.get(self.virus_field) == "Negative":
+                                    visit.virus = False
+                                elif row.get(self.virus_field) == "Positive":
+                                    visit.virus = True
+                            redcap_subject.visits.append(visit)
+
+        return result
+
+    def get_subject_query_data(self):
+        result = {
             'token': self.token,
             'content': 'record',
             'format': 'json',
             'type': 'flat',
-            'fields[0]': RED_CAP_DATE_BORN_FIELD,
-            'fields[1]': RED_CAP_SEX_FIELD,
-            'fields[2]': RED_CAP_ND_NUMBER_FIELD,
-            'fields[3]': RED_CAP_DEAD_FIELD,
-            'fields[4]': RED_CAP_LANGUAGE_1_FIELD,
-            'fields[5]': RED_CAP_LANGUAGE_2_FIELD,
-            'fields[6]': RED_CAP_LANGUAGE_3_FIELD,
-            'fields[7]': RED_CAP_LANGUAGE_4_FIELD,
-            'fields[8]': RED_CAP_MPOWER_ID_FIELD,
-            'events[0]': 'visit_1_arm_1',
+            'events[0]': 'visit_' + str(self.study.redcap_first_visit_number) + '_arm_1',
             'rawOrLabel': 'label',
             'rawOrLabelHeaders': 'raw',
             'exportCheckboxLabel': 'false',
@@ -279,24 +366,37 @@ class RedcapConnector(object):
             'exportDataAccessGroups': 'false',
             'returnFormat': 'json'
         }
-        data = self.execute_query(query_data)
-        result = []
-        for row in data:
-            redcap_subject = RedcapSubject()
-            redcap_subject.nd_number = row[RED_CAP_ND_NUMBER_FIELD]
-            redcap_subject.date_born = row[RED_CAP_DATE_BORN_FIELD]
-            redcap_subject.sex = row[RED_CAP_SEX_FIELD]
-            redcap_subject.dead = (row[RED_CAP_DEAD_FIELD].lower() == "yes")
-            redcap_subject.mpower_id = row[RED_CAP_MPOWER_ID_FIELD]
-            if row[RED_CAP_LANGUAGE_1_FIELD]:
-                redcap_subject.add_language(self.get_language(row[RED_CAP_LANGUAGE_1_FIELD]))
-            if row[RED_CAP_LANGUAGE_2_FIELD]:
-                redcap_subject.add_language(self.get_language(row[RED_CAP_LANGUAGE_2_FIELD]))
-            if row[RED_CAP_LANGUAGE_3_FIELD]:
-                redcap_subject.add_language(self.get_language(row[RED_CAP_LANGUAGE_3_FIELD]))
-            if row[RED_CAP_LANGUAGE_4_FIELD]:
-                redcap_subject.add_language(self.get_language(row[RED_CAP_LANGUAGE_4_FIELD]))
-            result.append(redcap_subject)
+        field_number = 0
+        if self.date_born_field != "":
+            result['fields[' + str(field_number) + ']'] = self.date_born_field
+            field_number += 1
+        if self.sex_field != "":
+            result['fields[' + str(field_number) + ']'] = self.sex_field
+            field_number += 1
+        if self.nd_number_field != "":
+            result['fields[' + str(field_number) + ']'] = self.nd_number_field
+            field_number += 1
+        if self.dead_field != "":
+            result['fields[' + str(field_number) + ']'] = self.dead_field
+            field_number += 1
+        if self.language_1_field != "":
+            result['fields[' + str(field_number) + ']'] = self.language_1_field
+            field_number += 1
+        if self.language_2_field != "":
+            result['fields[' + str(field_number) + ']'] = self.language_2_field
+            field_number += 1
+        if self.language_3_field != "":
+            result['fields[' + str(field_number) + ']'] = self.language_3_field
+            field_number += 1
+        if self.language_4_field != "":
+            result['fields[' + str(field_number) + ']'] = self.language_4_field
+            field_number += 1
+        if self.m_power_id_field != "":
+            result['fields[' + str(field_number) + ']'] = self.m_power_id_field
+            field_number += 1
+        if self.virus_field != "":
+            result['fields[' + str(field_number) + ']'] = self.virus_field
+            field_number += 1
         return result
 
     def get_language(self, name):
diff --git a/smash/web/static/js/smash.js b/smash/web/static/js/smash.js
index 2f2846523db2447089e98c1c7f08de6accf1aff3..fecda37d4b7560d1b16fd9ee768119189ea9138c 100644
--- a/smash/web/static/js/smash.js
+++ b/smash/web/static/js/smash.js
@@ -300,6 +300,9 @@ function createTable(params) {
     $(tableElement).find('tfoot div[name="yes_no_filter"]').each(function () {
         $(this).html('<select style="width:60px" ><option value selected="selected">---</option><option value="true">YES</option><option value="false">NO</option></select>');
     });
+    $(tableElement).find('tfoot div[name="yes_no_null_filter"]').each(function () {
+        $(this).html('<select style="width:60px" ><option value selected="selected">---</option><option value="true">YES</option><option value="false">NO</option><option value="null">N/A</option></select>');
+    });
     $(tableElement).find('tfoot div[name="integer_filter"]').each(function () {
         var options = '<option value selected="selected">---</option>';
         for (var i = 1; i <= 8; i++) {
diff --git a/smash/web/tests/importer/test_tns_csv_visit_import_reader.py b/smash/web/tests/importer/test_tns_csv_visit_import_reader.py
index 351988cd9ef810eb8229c80ff4b55a60811f6f14..f8ee5ce7db59a87c6a50a68b5b37af1efbbad8b5 100644
--- a/smash/web/tests/importer/test_tns_csv_visit_import_reader.py
+++ b/smash/web/tests/importer/test_tns_csv_visit_import_reader.py
@@ -14,7 +14,7 @@ from web.tests.functions import get_resource_path, create_study_subject, create_
 logger = logging.getLogger(__name__)
 
 
-class TestTnsCsvSubjectReader(TestCase):
+class TestTnsCsvVisitReader(TestCase):
     def setUp(self):
         self.warning_counter = MsgCounterHandler()
         logging.getLogger('').addHandler(self.warning_counter)
@@ -69,7 +69,7 @@ class TestTnsCsvSubjectReader(TestCase):
         self.assertEqual(4, appointment.datetime_when.month)
         self.assertEqual(2020, appointment.datetime_when.year)
 
-        self.assertEquals(1, self.get_warnings_count())
+        self.assertEquals(0, self.get_warnings_count())
 
     def test_load_data_with_existing_visit_and_appointment(self):
         filename = get_resource_path('tns_vouchers_import.csv')
@@ -97,7 +97,7 @@ class TestTnsCsvSubjectReader(TestCase):
         self.assertEqual(4, appointment.datetime_when.month)
         self.assertEqual(2020, appointment.datetime_when.year)
 
-        self.assertEquals(2, self.get_warnings_count())
+        self.assertEquals(0, self.get_warnings_count())
 
     def test_load_data_with_visit_and_no_previous_visits(self):
         filename = get_resource_path('tns_vouchers_3_import.csv')
@@ -130,7 +130,7 @@ class TestTnsCsvSubjectReader(TestCase):
         self.assertEqual("cov-000111", visit.subject.nd_number)
         self.assertEqual(1, visit.visit_number)
 
-        self.assertEquals(1, self.get_warnings_count())
+        self.assertEquals(0, self.get_warnings_count())
 
     def test_load_data_with_lab_id(self):
         filename = get_resource_path('tns_vouchers_lab_id_import.csv')
@@ -139,15 +139,15 @@ class TestTnsCsvSubjectReader(TestCase):
 
         visit = Visit.objects.filter(id=visits[0].id)[0]
         appointment = Appointment.objects.filter(visit=visit)[0]
-        self.assertEqual(u"Laboratoires réunis", appointment.location.name)
+        self.assertTrue(u"Laboratoires réunis" in appointment.location.name)
 
         visit = Visit.objects.filter(id=visits[1].id)[0]
         appointment = Appointment.objects.filter(visit=visit)[0]
-        self.assertEqual(u"Ketterthill", appointment.location.name)
+        self.assertTrue(u"Ketterthill" in appointment.location.name)
 
         visit = Visit.objects.filter(id=visits[2].id)[0]
         appointment = Appointment.objects.filter(visit=visit)[0]
-        self.assertEqual(u"BioneXt", appointment.location.name)
+        self.assertTrue(u"BioneXt" in appointment.location.name)
 
         self.assertEquals(3, self.get_warnings_count())
 
diff --git a/smash/web/views/kit.py b/smash/web/views/kit.py
index 2ce7e1341d53669bc0707c70804d857c6e917e15..374285be96003b08b48a4c330701e03a362ae330 100644
--- a/smash/web/views/kit.py
+++ b/smash/web/views/kit.py
@@ -4,8 +4,8 @@ import locale
 import logging
 import platform
 import time
+import traceback
 
-import pytz
 import timeout_decorator
 from django.contrib import messages
 from django.utils.dateparse import parse_datetime
@@ -14,10 +14,10 @@ from django_cron.models import CronJobLog
 
 from notifications import get_filter_locations, get_today_midnight_date
 from web.decorators import PermissionDecorator
-from web.models import ConfigurationItem, Language, Worker
+from web.models import ConfigurationItem, Language, Worker, Study
 from web.models.constants import KIT_RECIPIENT_EMAIL_CONFIGURATION_TYPE, KIT_DAILY_EMAIL_DAYS_PERIOD_TYPE, \
     KIT_EMAIL_HOUR_CONFIGURATION_TYPE, KIT_DAILY_EMAIL_TIME_FORMAT_TYPE, \
-    KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE, CRON_JOB_TIMEOUT
+    KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE, CRON_JOB_TIMEOUT, GLOBAL_STUDY_ID
 from . import wrap_response
 from ..forms import KitRequestForm
 from ..models import AppointmentType, Appointment
@@ -88,15 +88,8 @@ def kit_requests(request):
     return wrap_response(request, 'equipment_and_rooms/kit_requests/kit_requests.html', get_kit_requests_data(request))
 
 
-def send_mail(data):
-    time_format= ConfigurationItem.objects.get(type=KIT_DAILY_EMAIL_TIME_FORMAT_TYPE).value
-
-    end_date_str = " end of time"
-    if data["end_date"] is not None:
-        end_date_str = data["end_date"].strftime('%Y-%m-%d')
-    title = "Samples between " + \
-            data["start_date"].strftime('%Y-%m-%d') + " and " + end_date_str
-
+def create_detailed_email_content(data, title):
+    time_format = ConfigurationItem.objects.get(type=KIT_DAILY_EMAIL_TIME_FORMAT_TYPE).value
     cell_style = "padding: 8px; line-height: 1.42857143; vertical-align: top; " \
                  "font-size: 14px; font-family: 'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;"
 
@@ -139,6 +132,84 @@ def send_mail(data):
                       unicode(appointment.worker_assigned) + "</td>"
         email_body += "</tr>"
     email_body += "</tbody></table>"
+    return email_body
+
+
+def create_statistic_email_content(data, title):
+    time_format = ConfigurationItem.objects.get(type=KIT_DAILY_EMAIL_TIME_FORMAT_TYPE).value
+    cell_style = "padding: 8px; line-height: 1.42857143; vertical-align: top; " \
+                 "font-size: 14px; font-family: 'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;"
+
+    email_body = "<h1>" + title + "</h1>"
+
+    location_summary = {}
+
+    table = {}
+    for appointment in data["appointments"]:
+
+        appointment_date = appointment.datetime_when.strftime(time_format)
+        location = unicode(appointment.location)
+        if appointment.flying_team is not None:
+            location += " (" + unicode(appointment.flying_team) + ")"
+        simple_location = location.split(",")[0]
+        if location_summary.get(simple_location) is None:
+            location_summary[simple_location] = 0
+        location_summary[simple_location] += 1
+
+        if table.get(appointment_date) is None:
+            table[appointment_date] = {}
+        if table[appointment_date].get(location) is None:
+            table[appointment_date][location] = 0
+        table[appointment_date][location] += 1
+
+    email_body += 'Total number of donors scheduled: ' + str(len(data["appointments"])) + "</br></br>"
+    for location in location_summary:
+        email_body += 'Total number of donors scheduled at ' + location + ': ' + str(
+            location_summary[location]) + "</br>"
+
+    email_body += "</br>"
+
+    email_body += '<table style="border: 1px solid #f4f4f4;border-spacing: 0;border-collapse: collapse;">' \
+                  '<thead><tr>' \
+                  '<th>Date</th>' \
+                  '<th>Location</th>' \
+                  '<th>No-of-Donors</th>' \
+                  '</tr></thead>'
+    email_body += "<tbody>"
+
+    even = True
+    for appointment_date in table:
+        for location in table[appointment_date]:
+            subjects = table[appointment_date][location]
+            row_style = ""
+            even = not even
+            if even:
+                row_style = ' background-color: #f9f9f9;'
+            email_body += "<tr style='" + row_style + "'>"
+            email_body += "<td style='" + cell_style + "'>" + appointment_date + "</td>"
+            email_body += "<td style='" + cell_style + "'>" + location + "</td>"
+            email_body += "<td style='" + cell_style + "'>" + str(subjects) + "</td>"
+            email_body += "</tr>"
+    email_body += "</tbody></table>"
+    return email_body
+
+
+def send_mail(data):
+    end_date_str = " end of time"
+    title = None
+    if data["end_date"] is not None:
+        if (data["end_date"] - data["start_date"]).days < 2:
+            title = "Details of Donors scheduled for bio-sampling on " + data["start_date"].strftime('%Y-%m-%d')
+        end_date_str = data["end_date"].strftime('%Y-%m-%d')
+    if title is None:
+        title = "Samples between " + \
+                data["start_date"].strftime('%Y-%m-%d') + " and " + end_date_str
+
+    if Study.objects.get(id=GLOBAL_STUDY_ID).sample_mail_statistics:
+        email_body = create_statistic_email_content(data, title)
+    else:
+        email_body = create_detailed_email_content(data, title)
+
     recipients = ConfigurationItem.objects.get(
         type=KIT_RECIPIENT_EMAIL_CONFIGURATION_TYPE).value
     cc_recipients = []
@@ -153,6 +224,8 @@ def kit_requests_send_mail(request, start_date, end_date=None):
         send_mail(data)
         messages.add_message(request, messages.SUCCESS, 'Mail sent')
     except Exception as e:
+        traceback.print_exc()
+
         logger.warn('Kit Request Send Mail Failed: |{}|\n{}'.format(
             e.message, e.args))
         messages.add_message(request, messages.ERROR,
@@ -161,36 +234,34 @@ def kit_requests_send_mail(request, start_date, end_date=None):
 
 
 class KitRequestEmailSendJob(CronJobBase):
-    RUN_EVERY_MINUTES = 1
-    schedule = Schedule(run_every_mins=RUN_EVERY_MINUTES)
+    RUN_AT = []
+
+    try:
+        times = ConfigurationItem.objects.get(
+            type=KIT_EMAIL_HOUR_CONFIGURATION_TYPE).value.split(";")
+        for entry in times:
+            # TODO it's a hack assuming that we are in CEST
+            text = str((int(entry.split(":")[0]) + 22) % 24) + ":" + entry.split(":")[1]
+            RUN_AT.append(text)
+    except:
+        logger.error("Cannot fetch data about email hour")
+    schedule = Schedule(run_at_times=RUN_AT)
     code = 'web.kit_request_weekly_email'  # a unique code
 
     @timeout_decorator.timeout(CRON_JOB_TIMEOUT)
     def do(self):
-        now = datetime.datetime.utcnow()
-        hour = int(ConfigurationItem.objects.get(
-            type=KIT_EMAIL_HOUR_CONFIGURATION_TYPE).value.split(":")[0])
-        minute = int(ConfigurationItem.objects.get(
-            type=KIT_EMAIL_HOUR_CONFIGURATION_TYPE).value.split(":")[1])
-        # check if we sent email this day already
-        date = now.replace(hour=hour, minute=minute)
-        # TODO it's a hack assuming that we are in CEST
-        date = pytz.utc.localize(date - datetime.timedelta(minutes=122))
         jobs = CronJobLog.objects.filter(
-            code=KitRequestEmailSendJob.code, message="mail sent", start_time__gte=date)
+            code=KitRequestEmailSendJob.code, message="mail sent", start_time__gte=datetime.datetime.utcnow())
 
         if jobs.count() == 0:
-            if pytz.utc.localize(datetime.datetime.utcnow()) > date:
-                if self.match_day_of_week():
-                    worker = Worker.objects.create()
-                    data = get_kit_requests(worker)
-                    send_mail(data)
-                    worker.delete()
-                    return "mail sent"
-                else:
-                    return "day of week doesn't match"
+            if self.match_day_of_week():
+                worker = Worker.objects.create()
+                data = get_kit_requests(worker)
+                send_mail(data)
+                worker.delete()
+                return "mail sent"
             else:
-                return "too early"
+                return "day of week doesn't match"
         else:
             return "mail already sent"