diff --git a/requirements.txt b/requirements.txt
index 13d8015dcc58f1a0eb81bc182e761622c7ec2c9c..49a543a076f30f5d39b61889939d1ecfedebdedc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -33,6 +33,7 @@ mockito==1.2.2
 nexmo==2.3.0
 numpy==1.19.2
 pandas==1.1.3
+django-datatables-view==1.19.1
 phonenumberslite==8.9.14
 Pillow==3.4.2
 psycopg2==2.8.6
diff --git a/smash/web/admin.py b/smash/web/admin.py
index 6fce54fc40b50ea3a75960f36c6b53114360a0f8..dceea27fc8d9814aacd3d8e90a8c0e5868d1b4a9 100644
--- a/smash/web/admin.py
+++ b/smash/web/admin.py
@@ -1,16 +1,165 @@
 from django.contrib import admin
+from django.utils.html import format_html
 
 from .models import StudySubject, Item, Room, AppointmentType, Language, Location, Worker, FlyingTeam, Availability, \
-    Holiday, Visit, Appointment, StudyColumns, StudySubjectList, StudyVisitList, VisitColumns, SubjectColumns
+    Holiday, Visit, Appointment, StudyColumns, StudySubjectList, StudyVisitList, VisitColumns, SubjectColumns, \
+        Voucher, VoucherType, Provenance
 
+#good tutorial
+#https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Admin_site
 
 class LanguageAdmin(admin.ModelAdmin):
+    list_per_page = 20
     list_display = ('name', 'image_img')
 
+class VisitAdmin(admin.ModelAdmin):
+    list_per_page = 20
+    list_display = ('change_button', 'id', 'get_first_name', 'get_last_name', 'visit_number', 'get_start_date', 'get_end_date', 'get_status')
+    ordering = ('subject__subject__first_name', 'subject__subject__last_name', 'datetime_begin', 'datetime_end', 'visit_number')
+    list_filter = ('is_finished', 'visit_number', 'datetime_begin', 'datetime_end', 'appointment_types')
+    search_fields = ('subject__subject__first_name', 'subject__subject__last_name')
+
+    def get_first_name(self, obj):
+        return obj.subject.subject.first_name
+
+    def get_last_name(self, obj):
+        return obj.subject.subject.last_name
+
+    def get_start_date(self, obj):
+        return obj.datetime_begin.strftime('%Y-%m-%d')
+
+    def get_end_date(self, obj):
+        return obj.datetime_end.strftime('%Y-%m-%d')
+    
+    def get_status(self, obj):
+        return '✓' if obj.is_finished else ''
+
+    def change_button(self, obj):
+        return format_html('<a class="changelink" href="/admin/web/visit/{}/change/">Change</a>', obj.id)
+    change_button.short_description = ''
+
+    get_first_name.short_description = 'Subject First Name'
+    get_first_name.admin_order_field = 'subject__subject__first_name'
+
+    get_last_name.short_description = 'Subject Last Name'
+    get_last_name.admin_order_field = 'subject__subject__last_name'
+
+    get_start_date.short_description = 'Start Date'
+    get_start_date.admin_order_field = 'datetime_begin'
+
+    get_end_date.short_description = 'End Date'
+    get_end_date.admin_order_field = 'datetime_end'
+
+    get_status.short_description = 'Visit Finished'
+    get_status.admin_order_field = 'is_finished'
+
+class GeneralAppointmentFilter(admin.SimpleListFilter):
+    title = 'appointment type'
+    # Parameter for the filter that will be used in the URL query.
+    parameter_name = 'visit__isnull'
+
+    def lookups(self, request, model_admin):
+        return (
+            ('True', 'General Appointment'),
+            ('False', 'Visit Appointment'),
+        )
+
+    def queryset(self, request, queryset):
+        if self.value() == 'False':
+            return queryset.filter(visit__isnull=False)
+        if self.value() == 'True':
+            return queryset.filter(visit__isnull=True)
+
+class AppointmentAdmin(admin.ModelAdmin):
+    list_per_page = 20
+    list_display = ('change_button', 'id', 'get_datetime_when', 'get_length', 'location', 'status', 'get_first_name', 'get_last_name', 'get_visit_number', 'get_start_date', 'get_end_date', 'get_status', 'is_general_appointment')
+    ordering = ('datetime_when', 'length', 'location', 'visit__subject__subject__first_name', 'status', 'visit__subject__subject__last_name', 'visit__datetime_begin', 'visit__datetime_end', 'visit__visit_number')
+    list_filter = ('datetime_when', 'length', 'location', 'visit__is_finished', 'visit__visit_number', 'visit__datetime_begin', 'visit__datetime_end', 'appointment_types', 'status', GeneralAppointmentFilter)
+    search_fields = ('visit__subject__subject__first_name', 'visit__subject__subject__last_name')
+
+    def get_first_name(self, obj):
+        if obj.visit is not None:
+            return obj.visit.subject.subject.first_name
+        return None
+
+    def get_last_name(self, obj):
+        if obj.visit is not None:
+            return obj.visit.subject.subject.last_name
+        return None
+
+    def get_datetime_when(self, obj):
+        return obj.datetime_when.strftime('%Y-%m-%d %H:%M') 
+
+    def get_length(self, obj):
+        return obj.length
+
+    def get_start_date(self, obj):
+        if obj.visit is not None:
+            return obj.visit.datetime_begin.strftime('%Y-%m-%d')
+        return None
+
+    def get_end_date(self, obj):
+        if obj.visit is not None:
+            return obj.visit.datetime_end.strftime('%Y-%m-%d')
+        return None
+    
+    def get_status(self, obj):
+        if obj.visit is not None:
+            return '✓' if obj.visit.is_finished else ''
+        return None
+
+    def get_visit_number(self, obj):
+        if obj.visit is not None:
+            return obj.visit.visit_number
+        return None
+    
+    def is_general_appointment(self, obj):
+        return '✓' if obj.visit is None else ''
+
+    def change_button(self, obj):
+        return format_html('<a class="changelink" href="/admin/web/appointment/{}/change/">Change</a>', obj.id)
+    change_button.short_description = ''
+
+    get_first_name.short_description = 'Subject First Name'
+    get_first_name.admin_order_field = 'visit__subject__subject__first_name'
+
+    get_datetime_when.short_description = 'Appointment Date'
+    get_datetime_when.admin_order_field = 'datetime_when'
+
+    get_length.short_description = 'Appointment Duration (mins)'
+    get_length.admin_order_field = 'length'
+
+    get_start_date.short_description = 'Visit Start Date'
+    get_start_date.admin_order_field = 'visit__datetime_begin'
+
+    get_start_date.short_description = 'Visit Start Date'
+    get_start_date.admin_order_field = 'visit__datetime_begin'
+
+    get_end_date.short_description = 'Visit End Date'
+    get_end_date.admin_order_field = 'visit__datetime_end'
+
+    get_status.short_description = 'Visit Finished'
+    get_status.admin_order_field = 'visit__is_finished'
+
+    get_visit_number.short_description = 'Visit Number'
+    get_visit_number.admin_order_field = 'visit__visit_number'
+
+    is_general_appointment.short_description = 'General Appointment'
+
+class ProvenanceAdmin(admin.ModelAdmin):
+    list_per_page = 20
+    list_display = ('change_button', 'id', 'modified_table', 'modified_table_id', 'modification_date', 'modification_author', 'modified_field', 'previous_value', 'new_value', 'modification_description')
+    list_filter = ('modified_table', 'modification_date', 'modification_author', 'modified_field')
+    ordering = ('modified_table', 'modification_date', 'modification_author', 'modified_field')
+
+    def change_button(self, obj):
+        return format_html('<a class="changelink" href="/admin/web/provenance/{}/change/">Change</a>', obj.id)
+
+    change_button.short_description = ''
 
 # Register your models here.
 admin.site.register(StudySubject)
-admin.site.register(Visit)
+admin.site.register(Visit, VisitAdmin)
 admin.site.register(Item)
 admin.site.register(Room)
 admin.site.register(AppointmentType)
@@ -20,9 +169,12 @@ admin.site.register(Worker)
 admin.site.register(FlyingTeam)
 admin.site.register(Availability)
 admin.site.register(Holiday)
-admin.site.register(Appointment)
+admin.site.register(Appointment, AppointmentAdmin)
 admin.site.register(SubjectColumns)
 admin.site.register(StudyColumns)
 admin.site.register(StudySubjectList)
 admin.site.register(StudyVisitList)
 admin.site.register(VisitColumns)
+admin.site.register(Voucher)
+admin.site.register(VoucherType)
+admin.site.register(Provenance, ProvenanceAdmin) 
\ No newline at end of file
diff --git a/smash/web/api_urls.py b/smash/web/api_urls.py
index 22f3d57d57fc202b1e9cf0a6a780e15265c5602b..7dae498a26d5e610164cf4a544429a2f9dde03a7 100644
--- a/smash/web/api_urls.py
+++ b/smash/web/api_urls.py
@@ -16,7 +16,7 @@ Including another URLconf
 from django.conf.urls import url
 
 from web.api_views import worker, location, subject, appointment_type, appointment, configuration, daily_planning, \
-    redcap, flying_team, visit, voucher, voucher_type
+    redcap, flying_team, visit, voucher, voucher_type, provenance
 
 urlpatterns = [
     # appointments
@@ -35,6 +35,8 @@ urlpatterns = [
     # subjects data
     url(r'^cities$', subject.cities, name='web.api.cities'),
     url(r'^referrals$', subject.referrals, name='web.api.referrals'),
+    url(r'^export_log$', provenance.ExportLog.as_view(), name='web.api.export_log'),
+    url(r'^provenances$', provenance.CompleteLog.as_view(), name='web.api.provenances'),
     url(r'^subjects/(?P<type>[A-z]+)$', subject.subjects, name='web.api.subjects'),
     url(r'^subjects:columns/(?P<subject_list_type>[A-z]+)$', subject.get_subject_columns,
         name='web.api.subjects.columns'),
diff --git a/smash/web/api_views/provenance.py b/smash/web/api_views/provenance.py
new file mode 100644
index 0000000000000000000000000000000000000000..6fe1407dd1203360b50a2546c9118f7255978f66
--- /dev/null
+++ b/smash/web/api_views/provenance.py
@@ -0,0 +1,86 @@
+from ..models import Provenance, Worker
+from django_datatables_view.base_datatable_view import BaseDatatableView
+
+class ExportLog(BaseDatatableView):
+    model = Provenance
+    columns = [
+        'modification_date',
+        'modification_author',
+        'modification_description',
+        'request_path',
+        'request_ip_addr'
+    ]
+
+    # define column names that will be used in sorting
+    # order is important and should be same as order of columns
+    # displayed by datatables. For non sortable columns use empty
+    # value like ''
+    order_columns = [
+        'modification_date',
+        'modification_author',
+        'modification_description',
+        'request_path',
+        'request_ip_addr'
+    ]
+    
+    # set max limit of records returned, this is used to protect our site if someone tries to attack our site
+    # and make it return huge amount of data
+    max_display_length = 10
+
+    def filter_queryset(self, qs):
+        # simple example:
+        search = self.request.GET.get('search[value]', None)
+        if search != '' and search is not None:
+            inner_qs = Worker.objects.filter(first_name__icontains=search) | Worker.objects.filter(last_name__icontains=search)
+            qs = qs.filter(modification_author__in=inner_qs) | qs.filter(modification_description__icontains=search) | qs.filter(request_path__icontains=search) | qs.filter(request_ip_addr__icontains=search)
+        
+        qs = qs & qs.filter(modified_table__isnull=True, 
+                            previous_value__isnull=True, 
+                            new_value__isnull=True, 
+                            modified_field='', 
+                            request_path__startswith='/export/')
+
+        return qs
+
+class CompleteLog(BaseDatatableView):
+    model = Provenance
+    columns = ['modified_table',
+        'modified_table_id',
+        'modification_date',
+        'modification_author',
+        'modified_field',
+        'previous_value',
+        'new_value',
+        'modification_description',
+        'request_path',
+        'request_ip_addr'
+    ]
+
+    # define column names that will be used in sorting
+    # order is important and should be same as order of columns
+    # displayed by datatables. For non sortable columns use empty
+    # value like ''
+    order_columns = ['modified_table',
+        'modified_table_id',
+        'modification_date',
+        'modification_author',
+        'modified_field',
+        'previous_value',
+        'new_value',
+        'modification_description',
+        'request_path',
+        'request_ip_addr'
+    ]
+    
+    # set max limit of records returned, this is used to protect our site if someone tries to attack our site
+    # and make it return huge amount of data
+    max_display_length = 10
+
+    def filter_queryset(self, qs):
+        # simple example:
+        search = self.request.GET.get('search[value]', None)
+        if search != '' and search is not None:
+            inner_qs = Worker.objects.filter(first_name__icontains=search) | Worker.objects.filter(last_name__icontains=search)
+            qs = qs.filter(modification_author__in=inner_qs) | qs.filter(modification_description__icontains=search) | qs.filter(request_path__icontains=search) | qs.filter(request_ip_addr__icontains=search)
+
+        return qs
\ No newline at end of file
diff --git a/smash/web/management/commands/superworker.py b/smash/web/management/commands/superworker.py
new file mode 100644
index 0000000000000000000000000000000000000000..f34e7ebed76934e620438bbd509031a07fc28d6d
--- /dev/null
+++ b/smash/web/management/commands/superworker.py
@@ -0,0 +1,49 @@
+from getpass import getpass
+from django.core.management.base import BaseCommand
+from django.db import IntegrityError
+from ...models import Worker, User, Location, Language, WorkerStudyRole
+from web.models.worker_study_role import ROLE_CHOICES_TECHNICIAN
+from web.models.constants import GLOBAL_STUDY_ID
+
+class Command(BaseCommand):
+    help = 'creates super worker (superuser + worker)'
+
+    def add_arguments(self, parser):
+        parser.add_argument('-u', '--username', type=str, required=True)
+        parser.add_argument('-e', '--email', type=str, required=True)
+        parser.add_argument('-f', '--first-name', type=str, required=True)
+        parser.add_argument('-l', '--last-name', type=str, required=True)
+    
+    def handle(self, *args, **kwargs):
+        first_name = kwargs['first_name']
+        last_name = kwargs['last_name']
+        email = kwargs['email']
+        username = kwargs['username']
+
+        password = getpass()
+        user = None
+        try:
+            user = User.objects.create(username=username, email=email)
+            user.is_superuser = True
+            user.is_staff = True
+            user.is_admin = True
+            user.set_password(password)
+            user.save()
+        except IntegrityError:
+            self.stderr.write('User already exists')
+            return
+
+        try:
+            worker = Worker.objects.create(first_name=first_name, last_name=last_name, email=email, user=user)
+            locations = Location.objects.all()
+            worker.locations.set(locations)
+            languages = Language.objects.all()
+            worker.languages.set(languages)
+            worker.save()
+            workerStudyRole, _ = WorkerStudyRole.objects.update_or_create(worker=worker,
+                                                                          study_id=GLOBAL_STUDY_ID, name=ROLE_CHOICES_TECHNICIAN)
+        except IntegrityError:
+            self.stderr.write('Worker already exists')
+            return
+
+        self.stderr.write(f'Superworker {username} created')
\ No newline at end of file
diff --git a/smash/web/migrations/0173_auto_20201105_1142.py b/smash/web/migrations/0173_auto_20201105_1142.py
new file mode 100644
index 0000000000000000000000000000000000000000..9332a188532a7e0eee15b256a6b1fb9021ef9679
--- /dev/null
+++ b/smash/web/migrations/0173_auto_20201105_1142.py
@@ -0,0 +1,26 @@
+# Generated by Django 2.0.13 on 2020-11-05 11:42
+
+import django.core.files.storage
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0172_auto_20200525_1246'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='provenance',
+            name='modified_table',
+            field=models.CharField(max_length=1024, null=True, verbose_name='Modified table'),
+        ),
+        migrations.AlterField(
+            model_name='provenance',
+            name='modified_table_id',
+            field=models.IntegerField(default=0, null=True, verbose_name='Modified table row'),
+        )
+    ]
diff --git a/smash/web/migrations/0174_auto_20201105_1157.py b/smash/web/migrations/0174_auto_20201105_1157.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6f2abbbf6138a0280c07dbb1af099d65c7b58d1
--- /dev/null
+++ b/smash/web/migrations/0174_auto_20201105_1157.py
@@ -0,0 +1,21 @@
+# Generated by Django 2.0.13 on 2020-11-05 11:57
+
+import django.core.files.storage
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0173_auto_20201105_1142'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='provenance',
+            name='request_path',
+            field=models.CharField(blank=True, max_length=20480, null=True, verbose_name='Request Path'),
+        )
+    ]
diff --git a/smash/web/migrations/0175_auto_20201109_1404.py b/smash/web/migrations/0175_auto_20201109_1404.py
new file mode 100644
index 0000000000000000000000000000000000000000..005e1a94345f34d32671ffcfbbf7837cd7f2b63d
--- /dev/null
+++ b/smash/web/migrations/0175_auto_20201109_1404.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.0.13 on 2020-11-09 14:04
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0174_auto_20201105_1157'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='provenance',
+            name='request_ip_addr',
+            field=models.GenericIPAddressField(null=True, verbose_name='Request IP Address'),
+        )
+    ]
diff --git a/smash/web/models/appointment.py b/smash/web/models/appointment.py
index 55440a0a4ca530830b073ab8839fa4c764b69f50..d20214f26a02c4f49df9525db8e84af8d8d78473 100644
--- a/smash/web/models/appointment.py
+++ b/smash/web/models/appointment.py
@@ -77,6 +77,14 @@ class Appointment(models.Model):
         default=False
     )
 
+    def __str__(self):
+        start = self.datetime_when.strftime('%Y-%m-%d %H:%M')
+        if self.visit is not None:
+            subject = self.visit.subject.subject
+            return f'#{self.visit.visit_number:02} | {start} ({self.length} min) | {subject.first_name} {subject.last_name} | {self.status}'
+        else:
+            return f'{start} ({self.length} min) | {self.status}'
+
     def mark_as_finished(self):
         self.status = Appointment.APPOINTMENT_STATUS_FINISHED
         self.save()
diff --git a/smash/web/models/provenance.py b/smash/web/models/provenance.py
index 30a84fd9d6ccbae778c2f0291be52fdb3cffde82..b30ac078d90d91a667299142cce488684cbbf656 100644
--- a/smash/web/models/provenance.py
+++ b/smash/web/models/provenance.py
@@ -10,10 +10,10 @@ class Provenance(models.Model):
 
     modified_table = models.CharField(max_length=1024,
                                      verbose_name='Modified table',
-                                     blank=False, null=False
+                                     blank=False, null=True
                                     )
 
-    modified_table_id = models.IntegerField(default=0, verbose_name='Modified table row', blank=False, null=False)    
+    modified_table_id = models.IntegerField(default=0, verbose_name='Modified table row', blank=False, null=True)
 
     modification_date = models.DateTimeField(
         verbose_name='Modified on',
@@ -42,4 +42,11 @@ class Provenance(models.Model):
     modification_description = models.CharField(max_length=20480,
                                      verbose_name='Description',
                                      blank=False, null=False
-                                    )
\ No newline at end of file
+                                    )
+
+    request_path = models.CharField(max_length=20480,
+                                     verbose_name='Request Path',
+                                     blank=True, null=True
+                                    ) 
+    
+    request_ip_addr = models.GenericIPAddressField(verbose_name='Request IP Address', null=True)
\ No newline at end of file
diff --git a/smash/web/models/visit.py b/smash/web/models/visit.py
index fd67b459cb2a0841e1edeb25bd1ba44bb4789a34..5d7cec4eb4f236f8e87e8ce7dcae924920dbe358 100644
--- a/smash/web/models/visit.py
+++ b/smash/web/models/visit.py
@@ -47,11 +47,25 @@ class Visit(models.Model):
         default=1
     )
 
+    @property
+    def next_visit(self):
+        return Visit.objects.filter(subject=self.subject, visit_number=self.visit_number+1).order_by('datetime_begin','datetime_end').first()
+    
+    @property
+    def future_visits(self):
+        return Visit.objects.filter(subject=self.subject).filter(visit_number__gt=self.visit_number).order_by('datetime_begin','datetime_end')
+
     def __unicode__(self):
-        return "%s %s" % (self.subject.subject.first_name, self.subject.subject.last_name)
+        start = self.datetime_begin.strftime('%Y-%m-%d')
+        end   = self.datetime_end.strftime('%Y-%m-%d')
+        finished = '✓' if self.is_finished else ''
+        return f'#{self.visit_number:02} | {start} / {end} | {self.subject.subject.first_name} {self.subject.subject.last_name} | {finished}'
 
     def __str__(self):
-        return "%s %s" % (self.subject.subject.first_name, self.subject.subject.last_name)
+        start = self.datetime_begin.strftime('%Y-%m-%d')
+        end   = self.datetime_end.strftime('%Y-%m-%d')
+        finished = '✓' if self.is_finished else ''
+        return f'#{self.visit_number:02} | {start} / {end} | {self.subject.subject.first_name} {self.subject.subject.last_name} | {finished}'
 
     def mark_as_finished(self):
         self.is_finished = True
@@ -92,6 +106,32 @@ class Visit(models.Model):
                 datetime_end=visit_started + time_to_next_visit + relativedelta(months=study.default_visit_duration_in_months)
             )
 
+    def unfinish(self):
+        #if ValueError messages are changed, change test/view/test_visit.py
+        #check visit is indeed finished
+        if not self.is_finished:
+            raise ValueError('The visit is not finished.')
+        #check that there is only one future visit
+        future_visits = self.future_visits
+        if len(future_visits) > 1:
+            raise ValueError("Visit can't be unfinished. Only visits with one inmediate future visit (without appointments) can be unfinished.")
+        elif len(future_visits) == 1:
+            #check that the future visit has no appointments
+            #remove visit if it has no appointments
+            next_visit = future_visits[0]
+            if len(next_visit.appointment_set.all()) == 0:
+                next_visit.delete()
+            else:
+                raise ValueError("Visit can't be unfinished. The next visit has appointments.")
+            
+        else:
+            #this can happen when there is no auto follow up visit
+            pass
+        
+        self.is_finished = False
+        self.save()
+        return
+
 @receiver(post_save, sender=Visit)
 def check_visit_number(sender, instance, created, **kwargs):
     # no other solution to ensure the visit_number is in cronological order than to sort the whole list if there are future visits
diff --git a/smash/web/templates/export/index.html b/smash/web/templates/export/index.html
index fe8d952d03f0a8fc4ede565a05b3351dde977459..4e99d70b88409d4c55f35182c106db6c0c3f3cc9 100644
--- a/smash/web/templates/export/index.html
+++ b/smash/web/templates/export/index.html
@@ -1,9 +1,11 @@
 {% extends "_base.html" %}
 {% load static %}
+{% load filters %}
 
 {% block styles %}
     {{ block.super }}
     <link rel="stylesheet" href="{% static 'css/export.css' %}">
+    <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}">
     <style type="text/css">
         ul.list_of_fields{
           columns: 3;
@@ -92,37 +94,108 @@
             <li><a onclick="addFields(this, 'appointment_fields')" href="{% url 'web.views.export_to_csv' 'appointments' %}"><i class="fa fa-file-text-o"></i> CSV -
                 Text based</a></li>
         </ul>
+
+        <h3>Export Log</h3>
+
+        <div class="box-body">
+            <table id="table" class="table table-bordered table-striped">
+                <thead>
+                <tr>
+                    <th>Date</th>
+                    <th>Author</th>
+                    <th>Description</th>
+                    <th>Request Path</th>
+                    <th>Request IP</th>
+                </tr>
+                </thead>
+                <tbody>
+
+                </tbody>
+            </table>
+        </div>
+
     </div>
 
     <div class="box-body">
     </div>
-<script type="text/javascript">
-    window.onload = function() {
-        $('.list_of_fields_header').tooltip();
-        $('.list_of_fields_header').click(function(e){
-            $(e.target).children('i.fa').toggleClass('fa-expand');
-            $(e.target).children('i.fa').toggleClass('fa-compress');
-        });
-    };
-    function addFields(e, cls){
-        var fields = $(`.${cls} > li > input:checked`).map(function(i,e){
-            return e.value;
-        }).toArray().join(',');
-        //remove parameters from previous calls if any
-        //attention: we are assuming the original url doesn't have any parameter
-        $(e).attr('href', function() {
-            return this.href.split('?')[0] + `?fields=${fields}`;
-        });
-    }
-    function toggleCheckboxes(e){
-        if($(e).text().toLowerCase() == 'uncheck all'){
-            $(e).siblings('ul').find('input[type="checkbox"]').prop('checked', false);
-            $(e).text('Check All');
-        }else if($(e).text().toLowerCase() == 'check all'){
-            $(e).siblings('ul').find('input[type="checkbox"]').prop('checked', true);
-            $(e).text('Uncheck All');
-        }
-    }
 
-</script>
 {% endblock maincontent %}
+
+{% block scripts %}
+    {{ block.super }}
+
+    <script src="{% static 'AdminLTE/plugins/datatables/jquery.dataTables.min.js' %}"></script>
+    <script src="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.min.js' %}"></script>
+
+    <script>
+        window.onload = function() {
+            $('.list_of_fields_header').tooltip();
+            $('.list_of_fields_header').click(function(e){
+                $(e.target).children('i.fa').toggleClass('fa-expand');
+                $(e.target).children('i.fa').toggleClass('fa-compress');
+            });
+
+
+
+        };
+        function addFields(e, cls){
+            var fields = $(`.${cls} > li > input:checked`).map(function(i,e){
+                return e.value;
+            }).toArray().join(',');
+            //remove parameters from previous calls if any
+            //attention: we are assuming the original url doesn't have any parameter
+            $(e).attr('href', function() {
+                return this.href.split('?')[0] + `?fields=${fields}`;
+            });
+        }
+        function toggleCheckboxes(e){
+            if($(e).text().toLowerCase() == 'uncheck all'){
+                $(e).siblings('ul').find('input[type="checkbox"]').prop('checked', false);
+                $(e).text('Check All');
+            }else if($(e).text().toLowerCase() == 'check all'){
+                $(e).siblings('ul').find('input[type="checkbox"]').prop('checked', true);
+                $(e).text('Uncheck All');
+            }
+        }
+
+        $(function () {
+            console.log("{% url 'web.api.provenances' %}")
+            var oTable = $('#table').DataTable({
+                processing: true,
+                serverSide: true,
+                ordering: true,
+                autoWidth: false,
+                lengthChange: false,
+                columns: [
+                    {
+                        data: 'modification_date',
+                        orderable: true,
+                        searchable: true
+                    },
+                    {
+                        data: 'modification_author',
+                        orderable: true,
+                        searchable: true,
+                    },
+                    {
+                        data: 'modification_description',
+                        orderable: true,
+                        searchable: true,
+                    },
+                    {
+                        data: 'request_path',
+                        orderable: true,
+                        searchable: true,
+                    },
+                    {
+                        data: 'request_ip_addr',
+                        orderable: true,
+                        searchable: true,
+                    }
+                ],
+                order: [[ 0, "desc" ]],
+                ajax: "{% url 'web.api.export_log' %}"
+            });
+        });
+    </script>
+{% endblock scripts %}
\ No newline at end of file
diff --git a/smash/web/templates/provenance/list.html b/smash/web/templates/provenance/list.html
index 79eaacea0aa9857a30b74f11d83794036e7925b3..3405183742cf4e8bbce6e7aa5e21bb2a4f32420b 100644
--- a/smash/web/templates/provenance/list.html
+++ b/smash/web/templates/provenance/list.html
@@ -30,21 +30,11 @@
                 <th>Previous Value</th>
                 <th>New Value</th>
                 <th>Description</th>
+                <th>Request Path</th>
+                <th>Request IP</th>
             </tr>
             </thead>
             <tbody>
-            {% for provenance in provenances %}
-                <tr>
-                    <td>{{ provenance.modified_table }}</td>
-                    <td>{{ provenance.modified_table_id }}</td>
-                    <td data-sort="{{ provenance.modification_date | timestamp }}">{{ provenance.modification_date }}</td>
-                    <td>{{ provenance.modification_author }}</td>
-                    <td>{{ provenance.modified_field }}</td>
-                    <td>{{ provenance.previous_value }}</td>
-                    <td>{{ provenance.new_value }}</td>
-                    <td>{{ provenance.modification_description }}</td>
-                </tr>
-            {% endfor %}
             </tbody>
         </table>
     </div>
@@ -59,12 +49,68 @@
     <script>
         $(function () {
             $('#table').DataTable({
-                "paging": true,
-                "lengthChange": false,
-                "searching": true,
-                "ordering": true,
-                "info": true,
-                "autoWidth": false
+                paging: true,
+                lengthChange: false,
+                searching: true,
+                processing: true,
+                serverSide: true,
+                ordering: true,
+                order: [[ 2, "desc" ]],
+                info: true,
+                autoWidth: false,
+                columns: [
+                    {
+                        data: 'modified_table',
+                        orderable: true,
+                        searchable: true
+                    },
+                    {
+                        data: 'modified_table_id',
+                        orderable: true,
+                        searchable: true
+                    },
+                    {
+                        data: 'modification_date',
+                        orderable: true,
+                        searchable: true
+                    },
+                    {
+                        data: 'modification_author',
+                        orderable: true,
+                        searchable: true,
+                    },
+                    {
+                        data: 'modified_field',
+                        orderable: true,
+                        searchable: true,
+                    },
+                    {
+                        data: 'previous_value',
+                        orderable: true,
+                        searchable: true,
+                    },
+                    {
+                        data: 'new_value',
+                        orderable: true,
+                        searchable: true,
+                    },
+                    {
+                        data: 'modification_description',
+                        orderable: true,
+                        searchable: true,
+                    },
+                    {
+                        data: 'request_path',
+                        orderable: true,
+                        searchable: true,
+                    },
+                    {
+                        data: 'request_ip_addr',
+                        orderable: true,
+                        searchable: true,
+                    }
+                ],
+                ajax: "{% url 'web.api.provenances' %}"
             });
         });
     </script>
diff --git a/smash/web/templates/visits/details.html b/smash/web/templates/visits/details.html
index 6935b7d64949765f1b0b35e436c4640ff5f8f88e..862333277715f58fd0d16eccdffbcec45dd2b7f9 100644
--- a/smash/web/templates/visits/details.html
+++ b/smash/web/templates/visits/details.html
@@ -7,6 +7,15 @@
     <!-- DataTables -->
     <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}">
 
+    <style>
+        .tooltip-inner {
+            min-width: 150px;
+        }
+        div.disabled a {
+            cursor: not-allowed;
+        }
+    </style>
+
     {% include "includes/datepicker.css.html" %}
 {% endblock styles %}
 
@@ -60,11 +69,18 @@
 
                     <div class="col-md-6 form-group">
                         <label class="col-sm-4 control-label">
+                            {% if visFinished %}
+                                <i title="Only visits with one inmediate future visit (without appointments) can be unfinished." class="fa fa-info-circle"></i> 
+                            {% endif %}
                             Visit finished
                         </label>
+                        {% if visFinished %}
+                        <div class="col-sm-7">
+                        {% else %}
                         <div class="col-sm-8">
+                        {% endif %}
                             {% if visFinished %}
-                                <div class="btn btn-block">YES</div>
+                                <div class="btn btn-block btn-success disabled">YES</div>
                             {% else %}
                                 <div class="btn btn-block">
                                     {% if canFinish %}
@@ -76,6 +92,29 @@
                                 </div>
                             {% endif %}
                         </div>
+
+                        {% if visFinished %}
+                        <div class="col-sm-1">
+                            {% if "unfinish_visit" not in permissions %}
+                                <div title="You don't have permissions to unfinish visits." class="btn btn-block btn-warning disabled">
+                                    <i class="fa fa-undo"></i>
+                                </div>
+                            {% elif next_visit_appointments|length > 0 and visit.future_visits|length == 1 %} 
+                                <div title="This visit cannot be unfinished because next visit has appointments." class="btn btn-block btn-warning disabled">
+                                    <i class="fa fa-undo"></i>
+                                </div>
+                            {% elif visit.future_visits|length > 1 %} 
+                                <div title="This visit cannot be unfinished because there are more than one future visits." class="btn btn-block btn-warning disabled">
+                                    <i class="fa fa-undo"></i>
+                                </div>
+                            {% else %}
+                                    <a data-html="true" title="Unfinish this visit. </br> ⚠️ This will delete the next visit." class="btn btn-block btn-warning" 
+                                        href="{% url 'web.views.visit_unfinish' vid %}" onclick="return confirm('Are you sure you want to unfinish this visit? This will delete the next visit.')">
+                                    <i class="fa fa-undo"></i>
+                                    </a>
+                            {% endif %}
+                        </div>
+                        {% endif %}
                     </div>
                 </div><!-- /.box-body -->
                 <div class="box-footer">
@@ -225,6 +264,7 @@
     <script>
         var default_visit_duration_in_months = parseInt("{{default_visit_duration}}");
         visit_dates_behaviour($("[name='datetime_begin']"), $("[name='datetime_end']"), default_visit_duration_in_months);
+        $('[title]').tooltip();
     </script>
 
     {% include "includes/datepicker.js.html" %}
diff --git a/smash/web/tests/models/test_visit.py b/smash/web/tests/models/test_visit.py
index e02273f887262b86c5e88869333d60792219d5a5..48ba8358b7561ae6106eafb643c433ba0dff8154 100644
--- a/smash/web/tests/models/test_visit.py
+++ b/smash/web/tests/models/test_visit.py
@@ -107,6 +107,46 @@ class VisitModelTests(TestCase):
         visit_count = Visit.objects.filter(subject=subject).count()
         self.assertEqual(2, visit_count)
 
+    def test_future_visits(self):
+        subject = create_study_subject()
+        visit = create_visit(subject)
+
+        visit.mark_as_finished()
+
+        #assumes auto follow up visit
+        visit_count = Visit.objects.filter(subject=subject).count()
+        self.assertEqual(2, visit_count)
+
+        for fv in visit.future_visits:
+            self.assertGreater(fv.visit_number, visit.visit_number)
+
+    def test_next_visit(self):
+        subject = create_study_subject()
+        visit = create_visit(subject)
+
+        visit.mark_as_finished()
+
+        #assumes auto follow up visit
+        visit_count = Visit.objects.filter(subject=subject).count()
+        self.assertEqual(2, visit_count)
+
+        nv = Visit.objects.filter(subject=subject).order_by('datetime_begin','datetime_end').last()
+        self.assertEqual(visit.next_visit.id, nv.id)
+
+    def test_unfinish_visit(self):
+        subject = create_study_subject()
+        visit = create_visit(subject)
+
+        visit.mark_as_finished()
+
+        #assumes auto follow up visit
+        visit_count = Visit.objects.filter(subject=subject).count()
+        self.assertEqual(2, visit_count)
+
+        visit.unfinish()
+        visit_count = Visit.objects.filter(subject=subject).count()
+        self.assertEqual(1, visit_count)
+
     def test_mark_as_finished_2(self):
         study_subject = create_study_subject()
         visit = create_visit(study_subject)
diff --git a/smash/web/tests/view/test_visit.py b/smash/web/tests/view/test_visit.py
index 33e3df73ff0e523b3766f4f6ad61312be498261a..aa8108a9b695a9b7d660c4c97e924ae29719a5fe 100644
--- a/smash/web/tests/view/test_visit.py
+++ b/smash/web/tests/view/test_visit.py
@@ -10,6 +10,7 @@ from web.tests import LoggedInTestCase
 from web.tests.functions import create_study_subject, create_visit, create_appointment, create_appointment_type, \
     create_language, get_resource_path, format_form_field
 from web.views.notifications import get_today_midnight_date
+from django.contrib.messages import get_messages  
 
 logger = logging.getLogger(__name__)
 
@@ -100,6 +101,107 @@ class VisitViewTests(LoggedInTestCase):
         self.assertTrue(new_visit.is_finished)
         self.assertEqual(2, Visit.objects.count())
 
+    def test_unfinish_visit_without_permissions(self):
+        visit = create_visit()
+
+        self.assertFalse(visit.is_finished)
+
+        response = self.client.get(reverse('web.views.visit_mark', args=[visit.id, "finished"]))
+        self.assertEqual(response.status_code, 302)
+
+        new_visit = Visit.objects.get(id=visit.id)
+        self.assertTrue(new_visit.is_finished)
+        self.assertEqual(2, Visit.objects.count())
+
+        response = self.client.get(reverse('web.views.visit_unfinish', args=[visit.id]), follow=True)
+        messages = list(get_messages(response.wsgi_request))
+        self.assertEqual(len(messages), 1)
+        self.assertEqual(str(messages[0]), 'You are not authorized to view this page or perform this action. Request permissions to the system administrator.')
+        new_visit = Visit.objects.get(id=visit.id)
+        self.assertEqual(2, Visit.objects.count())
+        self.assertTrue(new_visit.is_finished)
+    
+    def test_unfinish_visit_with_future_visit_without_appointments(self):
+        self.login_as_admin()
+        visit = create_visit()
+
+        self.assertFalse(visit.is_finished)
+
+        response = self.client.get(reverse('web.views.visit_mark', args=[visit.id, "finished"]))
+        self.assertEqual(response.status_code, 302)
+
+        new_visit = Visit.objects.get(id=visit.id)
+        self.assertTrue(new_visit.is_finished)
+        self.assertEqual(2, Visit.objects.count())
+
+        response = self.client.get(reverse('web.views.visit_unfinish', args=[visit.id]), follow=True)
+        messages = list(get_messages(response.wsgi_request))
+        self.assertEqual(len(messages), 1)
+        self.assertEqual(str(messages[0]), 'Visit has been unfinished.')
+        new_visit = Visit.objects.get(id=visit.id)
+        self.assertEqual(1, Visit.objects.count())
+        self.assertFalse(new_visit.is_finished)
+
+    def test_unfinish_visit_with_future_visit_with_appointments(self):
+        self.login_as_admin()
+        visit = create_visit()
+
+        self.assertFalse(visit.is_finished)
+
+        response = self.client.get(reverse('web.views.visit_mark', args=[visit.id, "finished"]))
+        self.assertEqual(response.status_code, 302)
+
+        new_visit = Visit.objects.get(id=visit.id)
+        self.assertTrue(new_visit.is_finished)
+        self.assertEqual(2, Visit.objects.count())
+
+        create_appointment(new_visit.next_visit)
+
+        response = self.client.get(reverse('web.views.visit_unfinish', args=[visit.id]), follow=True)
+        messages = list(get_messages(response.wsgi_request))
+        self.assertEqual(len(messages), 1)
+        self.assertEqual(str(messages[0]), "Visit can't be unfinished. The next visit has appointments.")
+        original_visit = Visit.objects.get(id=visit.id)
+        self.assertTrue(original_visit.is_finished)
+
+    def test_unfinish_visit_with_two_future_visits(self):
+        self.login_as_admin()
+        visit = create_visit()
+        self.assertFalse(visit.is_finished)
+        response = self.client.get(reverse('web.views.visit_mark', args=[visit.id, "finished"]))
+        self.assertEqual(response.status_code, 302)
+
+        new_visit = Visit.objects.get(id=visit.id)
+        self.assertTrue(new_visit.is_finished)
+        self.assertEqual(2, Visit.objects.count())
+        
+        next_visit = new_visit.next_visit
+        response = self.client.get(reverse('web.views.visit_mark', args=[next_visit.id, "finished"]))
+        self.assertEqual(response.status_code, 302)
+        next_visit = Visit.objects.get(id=next_visit.id)
+        self.assertTrue(new_visit.is_finished)
+        self.assertEqual(3, Visit.objects.count())
+
+        #try to unfinish the original visit
+        response = self.client.get(reverse('web.views.visit_unfinish', args=[visit.id]), follow=True)
+        messages = list(get_messages(response.wsgi_request))
+        self.assertEqual(len(messages), 1)
+        self.assertEqual(str(messages[0]), "Visit can't be unfinished. Only visits with one inmediate future visit (without appointments) can be unfinished.")
+        self.assertEqual(3, Visit.objects.count())
+        original_visit = Visit.objects.get(id=visit.id)
+        self.assertTrue(original_visit.is_finished)
+
+    def test_unfinish_visit_which_is_not_finished(self):
+        self.login_as_admin()
+        visit = create_visit()
+
+        self.assertFalse(visit.is_finished)
+
+        response = self.client.get(reverse('web.views.visit_unfinish', args=[visit.id]), follow=True)
+        messages = list(get_messages(response.wsgi_request))
+        self.assertEqual(len(messages), 1)
+        self.assertEqual(str(messages[0]), "The visit is not finished.")
+
     def test_mark_as_finished_with_study_no_follow_up_rule(self):
         visit = create_visit()
         study = visit.subject.study
diff --git a/smash/web/urls.py b/smash/web/urls.py
index e8b61fa0aaf83e4b04cf9c3c1dc6dc908b829b10..651e74f0461c76cf28d7bfebc7d48d0ef8505ddd 100644
--- a/smash/web/urls.py
+++ b/smash/web/urls.py
@@ -69,6 +69,7 @@ urlpatterns = [
     url(r'^visits/missing_appointments$', views.visit.visits_with_missing_appointments,
         name='web.views.visits_with_missing_appointments'),
     url(r'^visits/details/(?P<id>\d+)$', views.visit.visit_details, name='web.views.visit_details'),
+    url(r'^visits/unfinish/(?P<id>\d+)$', views.visit.visit_unfinish, name='web.views.visit_unfinish'),
     url(r'^visits/add$', views.visit.visit_add, name='web.views.visit_add'),
     url(r'^visits/add/(?P<subject_id>\d+)$', views.visit.visit_add, name='web.views.visit_add'),
     url(r'^visit/mark/(?P<id>\d+)/(?P<as_what>[A-z]+)$', views.visit.visit_mark, name='web.views.visit_mark'),
diff --git a/smash/web/utils.py b/smash/web/utils.py
index dd61c944e1cc6089a2e6bf32132fabdbe3dd032d..54666468190a5c15bf1562b333687f7fb192c510 100644
--- a/smash/web/utils.py
+++ b/smash/web/utils.py
@@ -5,6 +5,13 @@ from datetime import timedelta
 
 from web.algorithm import VerhoeffAlgorithm, LuhnAlgorithm
 
+def get_client_ip(request):
+    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
+    if x_forwarded_for:
+        ip = x_forwarded_for.split(',')[0]
+    else:
+        ip = request.META.get('REMOTE_ADDR')
+    return ip
 
 def is_valid_social_security_number(number):
     if number is not None and number != '':
diff --git a/smash/web/views/export.py b/smash/web/views/export.py
index 82c1b68eb196a1f3fd8291f8c2ae2e54ee3de559..f6cd6b738cf9de931e7bf59acee72a5ddf1526d0 100644
--- a/smash/web/views/export.py
+++ b/smash/web/views/export.py
@@ -3,11 +3,11 @@ import csv
 
 import django_excel as excel
 from django.http import HttpResponse
-
+from ..utils import get_client_ip
 from .notifications import get_today_midnight_date
 from web.decorators import PermissionDecorator
 from . import e500_error, wrap_response
-from ..models import Subject, StudySubject, Appointment, ConfigurationItem
+from ..models import Subject, StudySubject, Appointment, ConfigurationItem, Worker, Provenance, Visit
 from web.models.constants import VISIT_SHOW_VISIT_NUMBER_FROM_ZERO
 from distutils.util import strtobool
 from web.templatetags.filters import display_visit_number
@@ -32,6 +32,16 @@ def export_to_csv(request, data_type="subjects"):
     for row in data:
         writer.writerow([s.encode("utf-8") for s in row])
 
+    worker = Worker.get_by_user(request.user)
+    ip = get_client_ip(request)
+    p = Provenance(
+                    modification_author=worker,
+                    modification_description=f'Export {data_type} to csv',
+                    modified_field='',
+                    request_path=request.path,
+                    request_ip_addr=ip)
+    p.save()
+
     return response
 
 
@@ -49,6 +59,16 @@ def export_to_excel(request, data_type="subjects"):
     response = excel.make_response_from_array(data, 'xls', file_name=filename)
     response['Content-Disposition'] = 'attachment; filename="' + filename + '"'
 
+    worker = Worker.get_by_user(request.user)
+    ip = get_client_ip(request)
+    p = Provenance(
+                    modification_author=worker,
+                    modification_description=f'Export {data_type} to excel',
+                    modified_field='',
+                    request_path=request.path,
+                    request_ip_addr=ip)
+    p.save()
+
     return response
 
 
diff --git a/smash/web/views/subject.py b/smash/web/views/subject.py
index 1583412ae89e5cb740e9a6fc3aab8581fdf71ee1..6922008ba35fd2140b4f9444f4c07146f4807cb5 100644
--- a/smash/web/views/subject.py
+++ b/smash/web/views/subject.py
@@ -3,7 +3,7 @@ import logging
 
 from django.contrib import messages
 from django.shortcuts import redirect, get_object_or_404
-
+from ..utils import get_client_ip
 from web.decorators import PermissionDecorator
 from . import wrap_response
 from ..forms import VisitDetailForm, SubjectAddForm, SubjectEditForm, StudySubjectAddForm, StudySubjectEditForm
@@ -72,6 +72,7 @@ def subject_edit(request, id):
     was_resigned = study_subject.resigned
     old_type = study_subject.type
     endpoint_was_reached = study_subject.endpoint_reached
+    ip = get_client_ip(request)
     if request.method == 'POST':
         study_subject_form = StudySubjectEditForm(request.POST, request.FILES, instance=study_subject,
                                                   was_resigned=was_resigned, prefix="study_subject", 
@@ -95,6 +96,8 @@ def subject_edit(request, id):
                                 modification_description = 'Worker "{}" changed study subject "{}" from "{}" to "{}"'.format(worker, 
                                                 study_subject.subject, old_value, new_value),
                                 modified_field = 'type',
+                                request_path=request.path,
+                                request_ip_addr=ip
                                 )
                 p.save()
             if subject_form.cleaned_data['dead'] and not was_dead:
@@ -106,6 +109,8 @@ def subject_edit(request, id):
                                 new_value = True,
                                 modification_description = 'Worker "{}" marks subject "{}" as dead'.format(worker, study_subject.subject),
                                 modified_field = 'dead',
+                                request_path=request.path,
+                                request_ip_addr=ip
                                 )
                 study_subject.subject.mark_as_dead()
                 p.save()
@@ -118,6 +123,8 @@ def subject_edit(request, id):
                                 new_value = True,
                                 modification_description = 'Worker "{}" marks study subject "{}" as resigned from study "{}"'.format(worker, study_subject.nd_number, study_subject.study),
                                 modified_field = 'resigned',
+                                request_path=request.path,
+                                request_ip_addr=ip
                                 )
                 study_subject.mark_as_resigned()
                 p.save()
diff --git a/smash/web/views/visit.py b/smash/web/views/visit.py
index d7d516cc616fe987d780c50d7d65bf34e6cda89d..6d60514fbe52ad5d4d9ff45acc062f7ce161b01d 100644
--- a/smash/web/views/visit.py
+++ b/smash/web/views/visit.py
@@ -1,6 +1,6 @@
 # coding=utf-8
 import logging
-
+from ..utils import get_client_ip
 from django.shortcuts import get_object_or_404, redirect
 from django.contrib import messages
 from .notifications import waiting_for_appointment
@@ -10,6 +10,7 @@ from web.models.study_visit_list import VISIT_LIST_GENERIC, VISIT_LIST_MISSING_A
 from . import wrap_response
 from ..forms import VisitDetailForm, VisitAddForm, SubjectDetailForm, StudySubjectDetailForm
 from ..models import Visit, Appointment, StudySubject, MailTemplate, Worker, Provenance
+from web.decorators import PermissionDecorator
 
 logger = logging.getLogger(__name__)
 
@@ -77,6 +78,10 @@ def visit_details(request, id):
         languages.append(study_subject.subject.default_written_communication_language)
     languages.extend(study_subject.subject.languages.all())
 
+    next_visit_appointments = []
+    if displayed_visit.next_visit is not None:
+        next_visit_appointments = displayed_visit.next_visit.appointment_set.all()
+
     return wrap_response(request, 'visits/details.html', {
         'default_visit_duration' : study_subject.study.default_visit_duration_in_months,
         'visit_form': visit_form,
@@ -87,11 +92,24 @@ def visit_details(request, id):
         'canFinish': can_finish,
         'vid': visit_id,
         'visit': displayed_visit,
-        'mail_templates': MailTemplate.get_visit_mail_templates(languages)})
+        'mail_templates': MailTemplate.get_visit_mail_templates(languages),
+        'next_visit': displayed_visit.next_visit,
+        'next_visit_appointments': next_visit_appointments,
+        })
 
+@PermissionDecorator('delete_visit', 'visit')
+def visit_unfinish(request, id):
+    visit = get_object_or_404(Visit, id=id)
+    try:
+        visit.unfinish()
+        messages.add_message(request, messages.SUCCESS, 'Visit has been unfinished.')
+    except ValueError as error:
+        messages.add_message(request, messages.ERROR, str(error))
+    return redirect('web.views.visit_details', id=id)
 
 def visit_mark(request, id, as_what):
     visit = get_object_or_404(Visit, id=id)
+    ip = get_client_ip(request)
     if as_what == 'finished':
         worker = Worker.get_by_user(request.user)
         p = Provenance(modified_table = Visit._meta.db_table,
@@ -101,6 +119,8 @@ def visit_mark(request, id, as_what):
                     new_value = True,
                     modification_description = 'Worker "{}" marked visit from "{}" as finished'.format(worker, visit.subject),
                     modified_field = 'is_finished',
+                    request_path=request.path,
+                    request_ip_addr=ip
             )
         visit.mark_as_finished()
         p.save()