diff --git a/apps/activity/api/serializers.py b/apps/activity/api/serializers.py
index 514515ef7a94c0081f39e5374aae42c1b86f6433..19b52a47e01ca2eb3dc8107c6653de753f162450 100644
--- a/apps/activity/api/serializers.py
+++ b/apps/activity/api/serializers.py
@@ -3,7 +3,7 @@
 
 from rest_framework import serializers
 
-from ..models import ActivityType, Activity, Guest
+from ..models import ActivityType, Activity, Guest, Entry
 
 
 class ActivityTypeSerializer(serializers.ModelSerializer):
@@ -37,3 +37,14 @@ class GuestSerializer(serializers.ModelSerializer):
     class Meta:
         model = Guest
         fields = '__all__'
+
+
+class EntrySerializer(serializers.ModelSerializer):
+    """
+    REST API Serializer for Entries.
+    The djangorestframework plugin will analyse the model `Entry` and parse all fields in the API.
+    """
+
+    class Meta:
+        model = Entry
+        fields = '__all__'
diff --git a/apps/activity/api/urls.py b/apps/activity/api/urls.py
index 79e0ba30db826b856170a54e5561674fc06c7037..3a2495fbae6f0dd47222441be9503a9728becda0 100644
--- a/apps/activity/api/urls.py
+++ b/apps/activity/api/urls.py
@@ -1,7 +1,7 @@
 # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
-from .views import ActivityTypeViewSet, ActivityViewSet, GuestViewSet
+from .views import ActivityTypeViewSet, ActivityViewSet, GuestViewSet, EntryViewSet
 
 
 def register_activity_urls(router, path):
@@ -11,3 +11,4 @@ def register_activity_urls(router, path):
     router.register(path + '/activity', ActivityViewSet)
     router.register(path + '/type', ActivityTypeViewSet)
     router.register(path + '/guest', GuestViewSet)
+    router.register(path + '/entry', EntryViewSet)
diff --git a/apps/activity/api/views.py b/apps/activity/api/views.py
index 9d106ee527a58caff1c69a3658f2ed1d769ebe43..764f2ac379a2a1d83f52d0889bf71a49e0964714 100644
--- a/apps/activity/api/views.py
+++ b/apps/activity/api/views.py
@@ -5,8 +5,8 @@ from django_filters.rest_framework import DjangoFilterBackend
 from rest_framework.filters import SearchFilter
 from api.viewsets import ReadProtectedModelViewSet
 
-from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer
-from ..models import ActivityType, Activity, Guest
+from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer, EntrySerializer
+from ..models import ActivityType, Activity, Guest, Entry
 
 
 class ActivityTypeViewSet(ReadProtectedModelViewSet):
@@ -43,3 +43,15 @@ class GuestViewSet(ReadProtectedModelViewSet):
     serializer_class = GuestSerializer
     filter_backends = [SearchFilter]
     search_fields = ['$last_name', '$first_name', '$inviter__alias__name', '$inviter__alias__normalized_name', ]
+
+
+class EntryViewSet(ReadProtectedModelViewSet):
+    """
+    REST API View set.
+    The djangorestframework plugin will get all `Entry` objects, serialize it to JSON with the given serializer,
+    then render it on /api/activity/entry/
+    """
+    queryset = Entry.objects.all()
+    serializer_class = EntrySerializer
+    filter_backends = [SearchFilter]
+    search_fields = ['$last_name', '$first_name', '$inviter__alias__name', '$inviter__alias__normalized_name', ]
diff --git a/apps/activity/models.py b/apps/activity/models.py
index 4bf92e23a431c11739f1b01d20aa1c800f19603d..9e3ea296f9dd1f15fd7766043818d117195c06ae 100644
--- a/apps/activity/models.py
+++ b/apps/activity/models.py
@@ -2,7 +2,9 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 from django.db import models
+from django.db.models import Q
 from django.utils.translation import gettext_lazy as _
+from rest_framework.exceptions import ValidationError
 from note.models import NoteUser, Transaction
 
 
@@ -103,7 +105,14 @@ class Activity(models.Model):
 
 
 class Entry(models.Model):
+    activity = models.ForeignKey(
+        Activity,
+        on_delete=models.PROTECT,
+        verbose_name=_("activity"),
+    )
+
     time = models.DateTimeField(
+        auto_now_add=True,
         verbose_name=_("entry time"),
     )
 
@@ -113,6 +122,47 @@ class Entry(models.Model):
         verbose_name=_("note"),
     )
 
+    guest = models.OneToOneField(
+        'activity.Guest',
+        on_delete=models.PROTECT,
+        null=True,
+    )
+
+    class Meta:
+        unique_together = (('activity', 'note', 'guest', ), )
+
+    def save(self, force_insert=False, force_update=False, using=None,
+             update_fields=None):
+
+        qs = Entry.objects.filter(~Q(pk=self.pk), activity=self.activity, note=self.note, guest=self.guest)
+        if qs.exists():
+            raise ValidationError(_("Already entered on ") + _("{:%Y-%m-%d %H:%M:%S}").format(qs.get().time, ))
+
+        if self.guest:
+            self.note = self.guest.inviter
+
+        insert = not self.pk
+        if insert:
+            if self.note.balance < 0:
+                raise ValidationError(_("The balance is negative."))
+
+        ret = super().save(force_insert, force_update, using, update_fields)
+
+        if insert and self.guest:
+            GuestTransaction.objects.create(
+                source=self.note,
+                source_alias=self.note.user.username,
+                destination=self.activity.organizer.note,
+                destination_alias=self.activity.organizer.name,
+                quantity=1,
+                amount=self.activity.activity_type.guest_entry_fee,
+                reason="Invitation " + self.activity.name + " " + self.guest.first_name + " " + self.guest.last_name,
+                valid=True,
+                guest=self.guest,
+            ).save()
+
+        return ret
+
 
 class Guest(models.Model):
     """
@@ -141,11 +191,14 @@ class Guest(models.Model):
         verbose_name=_("inviter"),
     )
 
-    entry = models.OneToOneField(
-        Entry,
-        on_delete=models.PROTECT,
-        null=True,
-    )
+    @property
+    def has_entry(self):
+        try:
+            if self.entry:
+                return True
+            return False
+        except AttributeError:
+            return False
 
     class Meta:
         verbose_name = _("guest")
diff --git a/apps/activity/tables.py b/apps/activity/tables.py
index 0e9466e4f8661187b439d1b0f51246744897d09d..449ee3212c8c97616f979e65800c9e7f67e4e163 100644
--- a/apps/activity/tables.py
+++ b/apps/activity/tables.py
@@ -35,8 +35,8 @@ class GuestTable(tables.Table):
         empty_values=(),
         attrs={
             "td": {
-                "class": lambda record: "" if record.entry else "validate btn btn-danger",
-                "onclick": lambda record: "" if record.entry else "remove_guest(" + str(record.pk) + ")"
+                "class": lambda record: "" if record.has_entry else "validate btn btn-danger",
+                "onclick": lambda record: "" if record.has_entry else "remove_guest(" + str(record.pk) + ")"
             }
         }
     )
@@ -50,8 +50,8 @@ class GuestTable(tables.Table):
         fields = ("last_name", "first_name", "inviter", )
 
     def render_entry(self, record):
-        if record.entry:
-            return str(record.date)
+        if record.has_entry:
+            return str(_("Entered on ") + str(_("{:%Y-%m-%d %H:%M:%S}").format(record.entry.time, )))
         return _("remove").capitalize()
 
 
@@ -83,6 +83,8 @@ class EntryTable(tables.Table):
         template_name = 'django_tables2/bootstrap4.html'
         row_attrs = {
             'class': 'table-row',
-            'id': lambda record: "row-" + str(record.type),
-            'data-href': lambda record: record.type
+            'id': lambda record: "row-" + ("guest-" if isinstance(record, Guest) else "membership-") + str(record.pk),
+            'data-type': lambda record: "guest" if isinstance(record, Guest) else "membership",
+            'data-id': lambda record: record.pk,
+            'data-inviter': lambda record: record.inviter.pk if isinstance(record, Guest) else "",
         }
diff --git a/apps/activity/views.py b/apps/activity/views.py
index 01b0e7f86ca775cea3271a6475cf378bbeace669..bb97c2c34fb398958c839cc148189b6b3b77dec6 100644
--- a/apps/activity/views.py
+++ b/apps/activity/views.py
@@ -86,13 +86,17 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
         if "search" in self.request.GET:
             pattern = self.request.GET["search"]
 
-        print(pattern)
+        if not pattern:
+            pattern = "^$"
+
+        if pattern[0] != "^":
+            pattern = "^" + pattern
 
         guest_qs = Guest.objects\
             .annotate(balance=F("inviter__balance"), note_name=F("inviter__user__username"))\
             .filter(Q(first_name__regex=pattern) | Q(last_name__regex=pattern)
                     | Q(inviter__alias__name__regex=pattern)
-                    | Q(inviter__alias__normalized_name__startswith=Alias.normalize(pattern)))\
+                    | Q(inviter__alias__normalized_name__regex=Alias.normalize(pattern)))\
             .distinct()[:20]
         for guest in guest_qs:
             guest.type = "Invité"
@@ -106,9 +110,9 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
             .filter(Q(note__polymorphic_ctype__model="noteuser")
                     & (Q(note__noteuser__user__first_name__regex=pattern)
                     | Q(note__noteuser__user__last_name__regex=pattern)
-                    | Q(name__regex="^" + pattern)
-                    | Q(normalized_name__startswith=Alias.normalize(pattern))))\
-            .distinct()[:20]
+                    | Q(name__regex=pattern)
+                    | Q(normalized_name__regex=Alias.normalize(pattern))))\
+            .distinct("username")[:20]
         for note in note_qs:
             note.type = "Adhérent"
             matched.append(note)
diff --git a/templates/activity/activity_entry.html b/templates/activity/activity_entry.html
index e09d0e8f103cc29a3c7f35a78410268ecade2eb2..c34d2b0a6b07719ddbdbd3d9d3149f862351c03d 100644
--- a/templates/activity/activity_entry.html
+++ b/templates/activity/activity_entry.html
@@ -8,6 +8,8 @@
 {% block content %}
     <input id="alias" type="text" class="form-control" placeholder="Nom/note ...">
 
+    <hr>
+
     <div id="entry_table">
         {% render_table table %}
     </div>
@@ -18,13 +20,55 @@
         old_pattern = null;
         alias_obj = $("#alias");
 
-        alias_obj.keyup(function() {
+        function reloadTable(force=false) {
             let pattern = alias_obj.val();
 
-            if (pattern === old_pattern || pattern === "")
+            if ((pattern === old_pattern || pattern === "") && !force)
                 return;
 
-            $("#entry_table").load(location.href + "?search=" + pattern.replace(" ", "%20") + " #entry_table");
-        });
+            $("#entry_table").load(location.href + "?search=" + pattern.replace(" ", "%20") + " #entry_table", init);
+            refreshBalance();
+        }
+
+        alias_obj.keyup(reloadTable);
+
+        $(document).ready(init);
+
+        function init() {
+            $(".table-row").click(function(e) {
+                let target = e.target.parentElement;
+                target = $("#" + target.id);
+
+                let type = target.attr("data-type");
+                let id = target.attr("data-id");
+
+                if (type === "membership") {
+                    $.post("/api/activity/entry/?format=json", {
+                        csrfmiddlewaretoken: CSRF_TOKEN,
+                        activity: {{ activity.id }},
+                        note: id,
+                        guest: null
+                    }).done(function () {
+                        addMsg("Entrée effectuée !", "success");
+                        reloadTable(true);
+                    }).fail(function(xhr) {
+                        errMsg(xhr.responseJSON);
+                    });
+                }
+                else {
+                }
+                $.post("/api/activity/entry/?format=json", {
+                        csrfmiddlewaretoken: CSRF_TOKEN,
+                        activity: {{ activity.id }},
+                        note: target.attr("data-inviter"),
+                        guest: id
+                    }).done(function () {
+                        addMsg("Entrée effectuée !", "success");
+                        reloadTable(true);
+                    }).fail(function(xhr) {
+                        errMsg(xhr.responseJSON);
+                    });
+            });
+        }
     </script>
 {% endblock %}