diff --git a/apps/activity/forms.py b/apps/activity/forms.py
index 1602eb6384ab1c80cebc79cc423d7bd9d4bc9aa1..7fafd15eb950d64282e829340e423ccca6930c2e 100644
--- a/apps/activity/forms.py
+++ b/apps/activity/forms.py
@@ -4,7 +4,7 @@
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from member.models import Club
-from note.models import NoteUser
+from note.models import NoteUser, Note
 from note_kfet.inputs import DateTimePickerInput, AutocompleteModelSelect
 
 from .models import Activity, Guest
@@ -13,12 +13,19 @@ from .models import Activity, Guest
 class ActivityForm(forms.ModelForm):
     class Meta:
         model = Activity
-        fields = '__all__'
+        exclude = ('valid', 'open', )
         widgets = {
             "organizer": AutocompleteModelSelect(
                 model=Club,
                 attrs={"api_url": "/api/members/club/"},
             ),
+            "note": AutocompleteModelSelect(
+                model=Note,
+                attrs={
+                    "api_url": "/api/note/note/",
+                    'placeholder': 'Note de l\'événement sur laquelle envoyer les crédits d\'invitation ...'
+                },
+            ),
             "attendees_club": AutocompleteModelSelect(
                 model=Club,
                 attrs={"api_url": "/api/members/club/"},
@@ -39,7 +46,7 @@ class GuestForm(forms.ModelForm):
                     'api_url': '/api/note/note/',
                     # We don't evaluate the content type at launch because the DB might be not initialized
                     'api_url_suffix':
-                        lambda value: '&polymorphic_ctype=' + str(ContentType.objects.get_for_model(NoteUser).pk),
+                        lambda: '&polymorphic_ctype=' + str(ContentType.objects.get_for_model(NoteUser).pk),
                     'placeholder': 'Note ...',
                 },
             ),
diff --git a/apps/activity/models.py b/apps/activity/models.py
index 7151a2a0569df0d7db61d9ca2d77acaac61f42b8..4bf92e23a431c11739f1b01d20aa1c800f19603d 100644
--- a/apps/activity/models.py
+++ b/apps/activity/models.py
@@ -3,7 +3,7 @@
 
 from django.db import models
 from django.utils.translation import gettext_lazy as _
-from note.models import NoteUser
+from note.models import NoteUser, Transaction
 
 
 class ActivityType(models.Model):
@@ -44,34 +44,59 @@ class Activity(models.Model):
         verbose_name=_('name'),
         max_length=255,
     )
+
     description = models.TextField(
         verbose_name=_('description'),
     )
+
     activity_type = models.ForeignKey(
         ActivityType,
         on_delete=models.PROTECT,
         related_name='+',
         verbose_name=_('type'),
     )
+
     organizer = models.ForeignKey(
         'member.Club',
         on_delete=models.PROTECT,
         related_name='+',
         verbose_name=_('organizer'),
     )
+
+    note = models.ForeignKey(
+        'note.Note',
+        on_delete=models.PROTECT,
+        related_name='+',
+        null=True,
+        blank=True,
+        verbose_name=_('note'),
+    )
+
     attendees_club = models.ForeignKey(
         'member.Club',
         on_delete=models.PROTECT,
         related_name='+',
         verbose_name=_('attendees club'),
     )
+
     date_start = models.DateTimeField(
         verbose_name=_('start date'),
     )
+
     date_end = models.DateTimeField(
         verbose_name=_('end date'),
     )
 
+    valid = models.BooleanField(
+        default=False,
+        verbose_name=_('valid'),
+    )
+
+    open = models.BooleanField(
+        default=False,
+        verbose_name=_('open'),
+    )
+
     class Meta:
         verbose_name = _("activity")
         verbose_name_plural = _("activities")
@@ -122,13 +147,17 @@ class Guest(models.Model):
         null=True,
     )
 
-    entry_transaction = models.ForeignKey(
-        'note.Transaction',
-        on_delete=models.PROTECT,
-        blank=True,
-        null=True,
-    )
-
     class Meta:
         verbose_name = _("guest")
         verbose_name_plural = _("guests")
+
+
+class GuestTransaction(Transaction):
+    guest = models.OneToOneField(
+        Guest,
+        on_delete=models.PROTECT,
+    )
+
+    @property
+    def type(self):
+        return _('Invitation')
diff --git a/apps/note/forms.py b/apps/note/forms.py
index 43c7b395520104a350347d8a299c92501da81196..8c9a04cea94f9e5ffe384cc087c47c24ecc606da 100644
--- a/apps/note/forms.py
+++ b/apps/note/forms.py
@@ -37,7 +37,7 @@ class TransactionTemplateForm(forms.ModelForm):
                         'api_url': '/api/note/note/',
                         # We don't evaluate the content type at launch because the DB might be not initialized
                         'api_url_suffix':
-                            lambda value: '&polymorphic_ctype=' + str(ContentType.objects.get_for_model(NoteClub).pk),
+                            lambda: '&polymorphic_ctype=' + str(ContentType.objects.get_for_model(NoteClub).pk),
                         'placeholder': 'Note ...',
                     },
                 ),
diff --git a/apps/permission/templatetags/perms.py b/apps/permission/templatetags/perms.py
index f1ecfacb71fbf62c1356f377550f9d62d5482725..aa2feeca1f6a110493e1626be689df06cc30cafd 100644
--- a/apps/permission/templatetags/perms.py
+++ b/apps/permission/templatetags/perms.py
@@ -48,9 +48,7 @@ def not_empty_model_change_list(model_name):
     return session.get("not_empty_model_change_list_" + model_name) == 1
 
 
-def has_perm(t, obj, field=None):
-    print(t)
-    perm = "." + t + ("__" + field if field else "_")
+def has_perm(perm, obj):
     return PermissionBackend().has_perm(get_current_authenticated_user(), perm, obj)
 
 
diff --git a/static/js/base.js b/static/js/base.js
index d21bd43389aa624c6fd149ac63557616068864ca..22d1366a88a672ea753fc91ea607236a32e0ce8a 100644
--- a/static/js/base.js
+++ b/static/js/base.js
@@ -28,15 +28,35 @@ function addMsg(msg, alert_type) {
         + msg + "</div>\n";
     msgDiv.html(html);
 }
+
 /**
  * add Muliple error message from err_obj
- * @param err_obj {error_code:erro_message}
+ * @param errs_obj [{error_code:erro_message}]
  */
 function errMsg(errs_obj){
     for (const err_msg of Object.values(errs_obj)) {
               addMsg(err_msg,'danger');
           }
 }
+
+var reloadWithTurbolinks = (function () {
+  var scrollPosition;
+
+  function reload () {
+    scrollPosition = [window.scrollX, window.scrollY];
+    Turbolinks.visit(window.location.toString(), { action: 'replace' })
+  }
+
+  document.addEventListener('turbolinks:load', function () {
+    if (scrollPosition) {
+      window.scrollTo.apply(window, scrollPosition);
+      scrollPosition = null
+    }
+  });
+
+  return reload;
+})();
+
 /**
  * Reload the balance of the user on the right top corner
  */
diff --git a/templates/activity/activity_detail.html b/templates/activity/activity_detail.html
index 339d087bc7b55cb24d7e6e55146e098f5f0a8a21..184cc6cddd1f52db65289f335e99929a913aac17 100644
--- a/templates/activity/activity_detail.html
+++ b/templates/activity/activity_detail.html
@@ -7,7 +7,7 @@
 
 {% block content %}
 
-    <div class="card bg-light shadow">
+    <div id="activity_info" class="card bg-light shadow">
         <div class="card-header text-center">
             <h4>{{ activity.name }}</h4>
         </div>
@@ -38,11 +38,23 @@
                     <dt class="col-xl-6">{% trans 'guest entry fee'|capfirst %}</dt>
                     <dd class="col-xl-6">{{ activity.activity_type.guest_entry_fee|pretty_money }}</dd>
                 {% endif %}
+
+                <dt class="col-xl-6">{% trans 'valid'|capfirst %}</dt>
+                <dd class="col-xl-6">{{ activity.valid|yesno }}</dd>
+
+                <dt class="col-xl-6">{% trans 'opened'|capfirst %}</dt>
+                <dd class="col-xl-6">{{ activity.open|yesno }}</dd>
             </dl>
         </div>
 
         <div class="card-footer text-center">
-            {% if "view"|has_perm:activity %}
+            {% if activity.valid and "change__open"|has_perm:activity %}
+                <a class="btn btn-warning btn-sm my-1" id="open_activity"> {% if activity.open %}{% trans "close"|capfirst %}{% else %}{% trans "open"|capfirst %}{% endif %}</a>
+            {% endif %}
+            {% if not activity.open and "change__valid"|has_perm:activity %}
+                <a class="btn btn-success btn-sm my-1" id="validate_activity"> {% if activity.valid %}{% trans "invalidate"|capfirst %}{% else %}{% trans "validate"|capfirst %}{% endif %}</a>
+            {% endif %}
+            {% if "view_"|has_perm:activity %}
                 <a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_update' pk=activity.pk %}"> {% trans "edit"|capfirst %}</a>
             {% endif %}
             {% if activity.activity_type.can_invite %}
@@ -77,5 +89,42 @@
           errMsg(xhr.responseJSON);
       });
     }
+
+    $("#open_activity").click(function() {
+        $.ajax({
+            url: "/api/activity/activity/{{ activity.pk }}/",
+            type: "PATCH",
+            dataType: "json",
+            headers: {
+                "X-CSRFTOKEN": CSRF_TOKEN
+            },
+            data: {
+                open: {{ activity.open|yesno:'false,true' }}
+            }
+        }).done(function () {
+            reloadWithTurbolinks();
+        }).fail(function (xhr) {
+            errMsg(xhr.responseJSON);
+        });
+    });
+
+    $("#validate_activity").click(function () {
+        console.log(42);
+        $.ajax({
+            url: "/api/activity/activity/{{ activity.pk }}/",
+            type: "PATCH",
+            dataType: "json",
+            headers: {
+                "X-CSRFTOKEN": CSRF_TOKEN
+            },
+            data: {
+                valid: {{ activity.valid|yesno:'false,true' }}
+            }
+        }).done(function () {
+            reloadWithTurbolinks();
+        }).fail(function (xhr) {
+            errMsg(xhr.responseJSON);
+        });
+    });
 </script>
 {% endblock %}