From cfc22159b40f7175e325d260d765e527a0cfaa7c Mon Sep 17 00:00:00 2001 From: Jonas 12t Date: Mon, 25 Oct 2021 15:54:55 +0400 Subject: [PATCH] =?UTF-8?q?Envoie=20vers=20Stripe=20et=20cr=C3=A9ation=20d?= =?UTF-8?q?u=20ticket?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DjangoFiles/Administration/admin_tenant.py | 25 ++- DjangoFiles/ApiBillet/serializers.py | 196 +++++++++++++++++++-- DjangoFiles/ApiBillet/urls.py | 1 + DjangoFiles/ApiBillet/views.py | 25 ++- DjangoFiles/BaseBillet/models.py | 86 +++++---- DjangoFiles/BaseBillet/validator.py | 15 -- DjangoFiles/PaiementStripe/views.py | 2 - 7 files changed, 279 insertions(+), 71 deletions(-) diff --git a/DjangoFiles/Administration/admin_tenant.py b/DjangoFiles/Administration/admin_tenant.py index 722396b..fd145d1 100644 --- a/DjangoFiles/Administration/admin_tenant.py +++ b/DjangoFiles/Administration/admin_tenant.py @@ -4,7 +4,7 @@ from django.contrib.auth.models import Group from solo.admin import SingletonModelAdmin from AuthBillet.models import HumanUser, SuperHumanUser, TermUser -from BaseBillet.models import Configuration, Event, OptionGenerale, Product, Price, Reservation, LigneArticle +from BaseBillet.models import Configuration, Event, OptionGenerale, Product, Price, Reservation, LigneArticle, Ticket from django.contrib.auth.admin import UserAdmin from Customers.models import Client @@ -133,6 +133,7 @@ class ConfigurationAdmin(SingletonModelAdmin): ('Billetterie', { 'fields': ( 'activer_billetterie', + 'name_required_for_ticket', 'jauge_max', 'option_generale_radio', 'option_generale_checkbox', @@ -175,16 +176,28 @@ class OptionGeneraleAdmin(admin.ModelAdmin): class ReservationAdmin(admin.ModelAdmin): list_display = ( - 'user_mail', - 'total_billet', - '_options_', - 'total_prix', - 'status' + 'datetime', + 'user_commande', + 'event', + 'status', ) readonly_fields = list_display staff_admin_site.register(Reservation, ReservationAdmin) + +class TicketAdmin(admin.ModelAdmin): + list_display = [ + 'first_name', + 'last_name', + 'reservation', + 'scan_status', + ] + readonly_fields = list_display + +staff_admin_site.register(Ticket, TicketAdmin) + + class ProductAdmin(admin.ModelAdmin): list_display = ( 'name', diff --git a/DjangoFiles/ApiBillet/serializers.py b/DjangoFiles/ApiBillet/serializers.py index a89cf6a..c2d81af 100644 --- a/DjangoFiles/ApiBillet/serializers.py +++ b/DjangoFiles/ApiBillet/serializers.py @@ -1,11 +1,14 @@ +from django.contrib.auth import get_user_model +from django.db import connection from rest_framework import serializers import json from django.utils.translation import gettext, gettext_lazy as _ from rest_framework.generics import get_object_or_404 -from BaseBillet.models import Event, Price, Product - - +import PaiementStripe +from AuthBillet.models import TibilletUser, HumanUser +from BaseBillet.models import Event, Price, Product, Reservation, Configuration, LigneArticle, Ticket +from PaiementStripe.views import creation_paiement_stripe class ProductSerializer(serializers.ModelSerializer): @@ -27,6 +30,7 @@ class ProductSerializer(serializers.ModelSerializer): 'prices', ] + class PriceSerializer(serializers.ModelSerializer): product = serializers.PrimaryKeyRelatedField(queryset=Product.objects.all()) @@ -72,7 +76,6 @@ class EventSerializer(serializers.ModelSerializer): read_only_fields = ['uuid', 'reservations'] depth = 1 - def validate(self, attrs): products = self.initial_data.get('products') @@ -96,12 +99,183 @@ class EventSerializer(serializers.ModelSerializer): instance.products.add(product) return instance -''' - -products = [ - {"uuid":"9340a9a1-1b90-488e-ab68-7b358b213dd7"}, - {"uuid":"60db1531-fd0a-4d92-a785-f384e77cd213"} -] + ''' + + products = [ + {"uuid":"9340a9a1-1b90-488e-ab68-7b358b213dd7"}, + {"uuid":"60db1531-fd0a-4d92-a785-f384e77cd213"} + ] + + + ''' + + +class ReservationSerializer(serializers.ModelSerializer): + class Meta: + model = Reservation + fields = [ + 'uuid', + 'datetime', + 'user_commande', + 'event', + 'status', + 'options', + 'tickets', + 'paiements', + ] + read_only_fields = [ + 'uuid', + 'datetime', + 'status', + ] + depth = 1 + + +class ReservationValidator(serializers.Serializer): + email = serializers.EmailField() + event = serializers.PrimaryKeyRelatedField(queryset=Event.objects.all()) + prices = serializers.JSONField(required=True) + + def validate_email(self, value): + User: TibilletUser = get_user_model() + user_paiement, created = User.objects.get_or_create( + email=value) + + if created: + user_paiement: HumanUser + user_paiement.client_source = connection.tenant + user_paiement.client_achat.add(connection.tenant) + user_paiement.is_active = False + else: + user_paiement.client_achat.add(connection.tenant) + user_paiement.save() + self.user_commande = user_paiement + return user_paiement.email + + def validate_prices(self, value): + print(value) + + # on vérifie que chaque article existe et a sa quantité. + # et qu'il y ai au moins un billet pour la reservation. + config = Configuration.get_solo() + self.nbr_ticket = 0 + self.prices_list = [] + for entry in value: + try: + price = Price.objects.get(pk=entry['uuid']) + price_object = { + 'price': price, + 'qty': float(entry['qty']), + } + + if price.product.categorie_article == Product.BILLET: + self.nbr_ticket += entry['qty'] + + # Si les noms sont requis pour la billetterie + if config.name_required_for_ticket and entry['qty'] > 0: + if not entry.get('customers'): + raise serializers.ValidationError(_(f'customers non trouvés')) + if len(entry.get('customers')) != entry['qty']: + raise serializers.ValidationError(_(f'nombre customers non conforme')) + + price_object['customers'] = entry.get('customers') + + self.prices_list.append(price_object) + + except Price.DoesNotExist as e: + raise serializers.ValidationError(_(f'price non trouvé : {e}')) + except ValueError as e: + raise serializers.ValidationError(_(f'qty doit être un entier ou un flottant : {e}')) + + ''' + products = [ + { + "uuid": "8c419d35-11a1-43b6-b500-b79db665d560", + "qty": 2, + "customers": [ + { + "first_name": "Jean-Michel", + "last_name": "Amoitié" + }, + { + "first_name": "Ellen", + "last_name": "Ripley" + } + ] + }, + { + "uuid": "c6e847d4-baaa-4d21-a4f0-a572b8319615", + "qty": 1, + "customers": [ + { + "first_name": "Douglas", + "last_name": "Adams" + } + ] + } + ] + + products = [ + { + "uuid": "8c419d35-11a1-43b6-b500-b79db665d560", + "qty": 2 + }, + { + "uuid": "c6e847d4-baaa-4d21-a4f0-a572b8319615", + "qty": 1 + } + ] + ''' + return value + + def validate(self, attrs): + if self.nbr_ticket == 0: + raise serializers.ValidationError(_(f'pas de billet dans la reservation')) + + config = Configuration.get_solo() + reservation = Reservation.objects.create( + user_commande=self.user_commande, + event=attrs.get('event'), + ) + + lignes_article = [] + for price in self.prices_list: + ligne_article = LigneArticle.objects.create( + reservation=reservation, + price=price.get('price'), + qty=price.get('qty'), + ) + lignes_article.append(ligne_article) + + if config.name_required_for_ticket and price.get('customers'): + for customer in price.get('customers'): + ticket = Ticket.objects.create( + reservation=reservation, + first_name=customer.get('first_name'), + last_name=customer.get('last_name'), + ) + + metadata = {'reservation':f'{reservation.uuid}'} + new_paiement_stripe = creation_paiement_stripe( + email_paiement=self.user_commande.email, + liste_ligne_article=lignes_article, + metadata=metadata, + absolute_domain=self.context.get('request').build_absolute_uri().partition('/api')[0], + ) + + if new_paiement_stripe.is_valid(): + print(new_paiement_stripe.checkout_session.stripe_id) + # return new_paiement_stripe.redirect_to_stripe() + self.checkout_session = new_paiement_stripe.checkout_session + return super().validate(attrs) + + else: + raise serializers.ValidationError(_(f'checkout strip not valid')) + + def to_representation(self, instance): + representation = super().to_representation(instance) + representation['checkout_url'] = self.checkout_session.url + # import ipdb;ipdb.set_trace() + return representation -''' \ No newline at end of file diff --git a/DjangoFiles/ApiBillet/urls.py b/DjangoFiles/ApiBillet/urls.py index aedc44e..bf5d0ac 100644 --- a/DjangoFiles/ApiBillet/urls.py +++ b/DjangoFiles/ApiBillet/urls.py @@ -8,6 +8,7 @@ router = routers.DefaultRouter() router.register(r'events', api_view.EventsViewSet, basename='event') router.register(r'products', api_view.ProductViewSet, basename='product') router.register(r'prices', api_view.TarifBilletViewSet, basename='price') +router.register(r'reservations', api_view.ReservationViewset, basename='reservation') urlpatterns = [ diff --git a/DjangoFiles/ApiBillet/views.py b/DjangoFiles/ApiBillet/views.py index d9dd359..d567ddb 100644 --- a/DjangoFiles/ApiBillet/views.py +++ b/DjangoFiles/ApiBillet/views.py @@ -4,10 +4,11 @@ from django.shortcuts import render from rest_framework.generics import get_object_or_404 from rest_framework.response import Response -from ApiBillet.serializers import EventSerializer, PriceSerializer, ProductSerializer +from ApiBillet.serializers import EventSerializer, PriceSerializer, ProductSerializer, ReservationSerializer, \ + ReservationValidator from AuthBillet.models import TenantAdminPermission from Customers.models import Client, Domain -from BaseBillet.models import Event, Price, Product +from BaseBillet.models import Event, Price, Product, Reservation from rest_framework import viewsets, permissions, status import os @@ -113,3 +114,23 @@ class EventsViewSet(viewsets.ViewSet): else: permission_classes = [TenantAdminPermission] return [permission() for permission in permission_classes] + + +class ReservationViewset(viewsets.ViewSet): + def list(self, request): + queryset = Reservation.objects.all().order_by('-datetime') + serializer = ReservationSerializer(queryset, many=True, context={'request': request}) + return Response(serializer.data) + + def create(self, request): + print(request.data) + validator = ReservationValidator(data=request.data, context={'request': request}) + if validator.is_valid(): + # serializer.save() + return Response(validator.data, status=status.HTTP_201_CREATED) + return Response(validator.errors, status=status.HTTP_400_BAD_REQUEST) + + + def get_permissions(self): + permission_classes = [TenantAdminPermission] + return [permission() for permission in permission_classes] \ No newline at end of file diff --git a/DjangoFiles/BaseBillet/models.py b/DjangoFiles/BaseBillet/models.py index f7d1868..30f5854 100644 --- a/DjangoFiles/BaseBillet/models.py +++ b/DjangoFiles/BaseBillet/models.py @@ -58,6 +58,8 @@ class Configuration(SingletonModel): adhesion_obligatoire = models.BooleanField(default=False) + name_required_for_ticket = models.BooleanField(default=False,verbose_name=_("Billet nominatifs")) + carte_restaurant = StdImageField(upload_to='images/', null=True, blank=True, validators=[MaxSizeValidator(1920, 1920)], @@ -118,7 +120,6 @@ class Configuration(SingletonModel): ) - class Product(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True) @@ -191,6 +192,7 @@ class Product(models.Model): self.id_product_stripe = None self.save() + class Price(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True) @@ -223,7 +225,6 @@ class Price(models.Model): def __str__(self): return f"{self.product.name} {self.name}" - def get_id_price_stripe(self): configuration = Configuration.get_solo() if configuration.stripe_api_key and not self.id_price_stripe: @@ -236,6 +237,7 @@ class Price(models.Model): unit_amount=int("{0:.2f}".format(self.prix).replace('.', '')), currency="eur", product=self.product.get_id_product_stripe(), + nickname= self.name, ) self.id_price_stripe = price.id @@ -306,7 +308,8 @@ class Event(models.Model): class Reservation(models.Model): - uuid = models.UUIDField(primary_key=True, db_index=True, default=uuid.uuid4) + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True) + datetime = models.DateTimeField(auto_now=True) user_commande = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT) @@ -314,46 +317,40 @@ class Reservation(models.Model): on_delete=models.PROTECT, related_name="reservation") - ANNULEE, MAIL_NON_VALIDEE, NON_PAYEE, VALIDEE, PAYEE = 'NAN', 'MNV', 'NPA', 'VAL', 'PAY' + CANCELED, UNPAID, PAID, VALID, = 'C', 'N', 'P', 'V' TYPE_CHOICES = [ - (ANNULEE, _('Annulée')), - (MAIL_NON_VALIDEE, _('Email non validé')), - (NON_PAYEE, _('Non payée')), - (VALIDEE, _('Validée')), - (PAYEE, _('Payée')), + (CANCELED, _('Annulée')), + (UNPAID, _('Non payée')), + (PAID, _('Payée')), + (VALID, _('Validée')), ] - status = models.CharField(max_length=3, choices=TYPE_CHOICES, default=NON_PAYEE, + status = models.CharField(max_length=3, choices=TYPE_CHOICES, default=UNPAID, verbose_name=_("Status de la réservation")) options = models.ManyToManyField(OptionGenerale) - def __str__(self): - return self.user_commande.email - def user_mail(self): return self.user_commande.email - - def total_billet(self): - total = 0 - for ligne in self.lignearticle_set.all(): - if ligne.billet: - total += ligne.qty - return total - - def total_prix(self): - total = 0 - for ligne in self.lignearticle_set.all(): - if ligne.product: - total += ligne.qty * ligne.product.prix - if ligne.billet: - total += ligne.qty * ligne.billet.prix - - return total - - def _options_(self): - return " - ".join([f"{option.name}" for option in self.options.all()]) - + # + # def total_billet(self): + # total = 0 + # for ligne in self.paiements.all(): + # if ligne.billet: + # total += ligne.qty + # return total + # + # def total_prix(self): + # total = 0 + # for ligne in self.paiements.all(): + # if ligne.product: + # total += ligne.qty * ligne.product.prix + # + # return total + # + # def _options_(self): + # return " - ".join([f"{option.name}" for option in self.options.all()]) + # @receiver(post_save, sender=Reservation) def verif_mail_valide(sender, instance: Reservation, created, **kwargs): @@ -364,6 +361,25 @@ def verif_mail_valide(sender, instance: Reservation, created, **kwargs): instance.save() +class Ticket(models.Model): + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True) + + first_name = models.CharField(max_length=200) + last_name = models.CharField(max_length=200) + + reservation = models.ForeignKey(Reservation, on_delete=models.CASCADE, related_name="tickets") + + NOT_ACTIV, NOT_SCANNED, SCANNED = 'N', 'K', 'S' + SCAN_CHOICES = [ + (NOT_ACTIV, _('Non actif')), + (NOT_SCANNED, _('Non scanné')), + (SCANNED, _('scanné')), + ] + + scan_status = models.CharField(max_length=1, choices=SCAN_CHOICES, default=NOT_ACTIV, + verbose_name=_("Status du scan")) + + class LigneArticle(models.Model): uuid = models.UUIDField(primary_key=True, db_index=True, default=uuid.uuid4) datetime = models.DateTimeField(auto_now=True) @@ -372,7 +388,7 @@ class LigneArticle(models.Model): qty = models.SmallIntegerField() - reservation = models.ForeignKey(Reservation, on_delete=models.CASCADE, blank=True, null=True) + reservation = models.ForeignKey(Reservation, on_delete=models.CASCADE, blank=True, null=True, related_name='paiements') carte = models.ForeignKey(CarteCashless, on_delete=models.PROTECT, blank=True, null=True) paiement_stripe = models.ForeignKey(Paiement_stripe, on_delete=models.PROTECT, blank=True, null=True) diff --git a/DjangoFiles/BaseBillet/validator.py b/DjangoFiles/BaseBillet/validator.py index 5190c99..2125ba7 100644 --- a/DjangoFiles/BaseBillet/validator.py +++ b/DjangoFiles/BaseBillet/validator.py @@ -12,7 +12,6 @@ class ReservationValidator(serializers.Serializer): radio_generale = serializers.PrimaryKeyRelatedField(queryset=OptionGenerale.objects.all(), many=True) option_checkbox = serializers.PrimaryKeyRelatedField(queryset=OptionGenerale.objects.all(), many=True) articles = serializers.ListField() - billets = serializers.ListField() def validate_articles(self, value): value_dict = {} @@ -22,17 +21,3 @@ class ReservationValidator(serializers.Serializer): value_dict[pk] = qty return value_dict - - def validate_billets(self, value): - value_dict = {} - billet_obj = Price.objects.all() - for couple in value: - pk, qty = billet_obj.get(pk=couple.split(',')[0]), int(couple.split(',')[1]) - value_dict[pk] = qty - - return value_dict - # - # # if value <= configuration.max_per_user : - # return value - # else : - # raise serializers.ValidationError(_(f"Pas plus de {configuration.max_per_user} places en même temps.")) diff --git a/DjangoFiles/PaiementStripe/views.py b/DjangoFiles/PaiementStripe/views.py index 5988583..901adbc 100644 --- a/DjangoFiles/PaiementStripe/views.py +++ b/DjangoFiles/PaiementStripe/views.py @@ -103,8 +103,6 @@ class creation_paiement_stripe(): return line_items def _checkout_session(self): - - checkout_session = stripe.checkout.Session.create( success_url=f'{self.absolute_domain}/stripe/return/{self.paiement_stripe_db.uuid}', cancel_url=f'{self.absolute_domain}/stripe/return/{self.paiement_stripe_db.uuid}',