forked from DGNum/gestioCOF
Merge branch 'Aufinal/bda_revente' into 'master'
Aufinal/bda revente Nouvelle interface BDA-Revente: - on s'inscrit pour recevoir les reventes sur `liste-revente`, où on peut aussi récupérer les places au shotgun - on coche les places qu'on veut revendre sur `bda/places`, et on dispose d'une heure pour changer d'avis - quand une place est revendue, on envoie un mail à tous les gens inscrits ; ils ont un délai pour répondre (dépendant du temps restant avant le spectacle) - on fait un tirage au sort entre ceux ayant répondu See merge request !68
This commit is contained in:
commit
d8eb5786a8
21 changed files with 648 additions and 46 deletions
18
bda/admin.py
18
bda/admin.py
|
@ -9,7 +9,7 @@ from django.core.mail import send_mail
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db.models import Sum, Count
|
from django.db.models import Sum, Count
|
||||||
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
||||||
Attribution, Tirage, Quote, CategorieSpectacle
|
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
@ -210,6 +210,21 @@ class SalleAdmin(admin.ModelAdmin):
|
||||||
search_fields = ('name', 'address')
|
search_fields = ('name', 'address')
|
||||||
|
|
||||||
|
|
||||||
|
class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||||
|
model = SpectacleRevente
|
||||||
|
|
||||||
|
def spectacle(self, obj):
|
||||||
|
return obj.attribution.spectacle
|
||||||
|
|
||||||
|
list_display = ("spectacle", "seller", "date", "soldTo")
|
||||||
|
raw_id_fields = ("attribution",)
|
||||||
|
readonly_fields = ("shotgun", "expiration_time")
|
||||||
|
search_fields = ("spectacle__title",
|
||||||
|
"seller__user__username",
|
||||||
|
"seller__user__firstname",
|
||||||
|
"seller__user__lastname",)
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(CategorieSpectacle)
|
admin.site.register(CategorieSpectacle)
|
||||||
admin.site.register(Spectacle, SpectacleAdmin)
|
admin.site.register(Spectacle, SpectacleAdmin)
|
||||||
admin.site.register(Salle, SalleAdmin)
|
admin.site.register(Salle, SalleAdmin)
|
||||||
|
@ -217,3 +232,4 @@ admin.site.register(Participant, ParticipantAdmin)
|
||||||
admin.site.register(Attribution, AttributionAdmin)
|
admin.site.register(Attribution, AttributionAdmin)
|
||||||
admin.site.register(ChoixSpectacle, ChoixSpectacleAdmin)
|
admin.site.register(ChoixSpectacle, ChoixSpectacleAdmin)
|
||||||
admin.site.register(Tirage, TirageAdmin)
|
admin.site.register(Tirage, TirageAdmin)
|
||||||
|
admin.site.register(SpectacleRevente, SpectacleReventeAdmin)
|
||||||
|
|
50
bda/forms.py
50
bda/forms.py
|
@ -4,9 +4,13 @@ from __future__ import division
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms.models import BaseInlineFormSet
|
from django.forms.models import BaseInlineFormSet
|
||||||
from bda.models import Spectacle
|
from django.db.models import Q
|
||||||
|
from django.utils import timezone
|
||||||
|
from bda.models import Attribution, Spectacle
|
||||||
|
|
||||||
|
|
||||||
class BaseBdaFormSet(BaseInlineFormSet):
|
class BaseBdaFormSet(BaseInlineFormSet):
|
||||||
|
@ -35,17 +39,47 @@ class TokenForm(forms.Form):
|
||||||
token = forms.CharField(widget=forms.widgets.Textarea())
|
token = forms.CharField(widget=forms.widgets.Textarea())
|
||||||
|
|
||||||
|
|
||||||
class SpectacleModelChoiceField(forms.ModelChoiceField):
|
class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
||||||
def label_from_instance(self, obj):
|
def label_from_instance(self, obj):
|
||||||
return "%s le %s (%s) à %.02f€" % (obj.title, obj.date_no_seconds(),
|
return "%s" % obj.spectacle
|
||||||
obj.location, obj.price)
|
|
||||||
|
|
||||||
|
|
||||||
class ResellForm(forms.Form):
|
class ResellForm(forms.Form):
|
||||||
count = forms.ChoiceField(choices=(("1", "1"), ("2", "2"),))
|
attributions = AttributionModelMultipleChoiceField(
|
||||||
spectacle = SpectacleModelChoiceField(queryset=Spectacle.objects.none())
|
queryset=Attribution.objects.none(),
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
required=False)
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
def __init__(self, participant, *args, **kwargs):
|
||||||
super(ResellForm, self).__init__(*args, **kwargs)
|
super(ResellForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['spectacle'].queryset = participant.attributions.all() \
|
self.fields['attributions'].queryset = participant.attribution_set\
|
||||||
.distinct()
|
.filter(spectacle__date__gte=timezone.now())\
|
||||||
|
.exclude(revente__seller=participant)
|
||||||
|
|
||||||
|
|
||||||
|
class AnnulForm(forms.Form):
|
||||||
|
attributions = AttributionModelMultipleChoiceField(
|
||||||
|
queryset=Attribution.objects.none(),
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
def __init__(self, participant, *args, **kwargs):
|
||||||
|
super(AnnulForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields['attributions'].queryset = participant.attribution_set\
|
||||||
|
.filter(spectacle__date__gte=timezone.now(),
|
||||||
|
revente__isnull=False,
|
||||||
|
revente__date__gte=timezone.now()-timedelta(hours=1))\
|
||||||
|
.filter(Q(revente__soldTo__isnull=True) |
|
||||||
|
Q(revente__soldTo=participant))
|
||||||
|
|
||||||
|
|
||||||
|
class InscriptionReventeForm(forms.Form):
|
||||||
|
spectacles = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Spectacle.objects.none(),
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
def __init__(self, tirage, *args, **kwargs):
|
||||||
|
super(InscriptionReventeForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields['spectacles'].queryset = tirage.spectacle_set.filter(
|
||||||
|
date__gte=timezone.now())
|
||||||
|
|
36
bda/management/commands/manage_reventes.py
Normal file
36
bda/management/commands/manage_reventes.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from django.utils import timezone
|
||||||
|
from datetime import timedelta
|
||||||
|
from bda.models import SpectacleRevente
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Envoie les mails de notification et effectue " \
|
||||||
|
"les tirages au sort des reventes"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
now = timezone.now()
|
||||||
|
self.stdout.write(now)
|
||||||
|
reventes = SpectacleRevente.objects.all()
|
||||||
|
for revente in reventes:
|
||||||
|
# Check si < 24h
|
||||||
|
if (revente.attribution.spectacle.date <=
|
||||||
|
revente.date + timedelta(days=1)) and \
|
||||||
|
now >= revente.date + timedelta(minutes=15) and \
|
||||||
|
not revente.notif_sent:
|
||||||
|
revente.mail_shotgun()
|
||||||
|
self.stdout.write("Mail de disponibilité immédiate envoyé")
|
||||||
|
# Check si délai de retrait dépassé
|
||||||
|
elif (now >= revente.date + timedelta(hours=1) and
|
||||||
|
not revente.notif_sent):
|
||||||
|
revente.send_notif()
|
||||||
|
self.stdout.write("Mail d'inscription à une revente envoyé")
|
||||||
|
# Check si tirage à faire
|
||||||
|
elif (now >= revente.expiration_time and
|
||||||
|
not revente.tirage_done):
|
||||||
|
revente.tirage()
|
||||||
|
self.stdout.write("Tirage effectué, mails envoyés")
|
59
bda/migrations/0009_revente.py
Normal file
59
bda/migrations/0009_revente.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('bda', '0008_py3'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SpectacleRevente',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False,
|
||||||
|
auto_created=True, primary_key=True)),
|
||||||
|
('date', models.DateTimeField(
|
||||||
|
default=django.utils.timezone.now,
|
||||||
|
verbose_name='Date de mise en vente')),
|
||||||
|
('notif_sent', models.BooleanField(
|
||||||
|
default=False, verbose_name='Notification envoy\xe9e')),
|
||||||
|
('tirage_done', models.BooleanField(
|
||||||
|
default=False, verbose_name='Tirage effectu\xe9')),
|
||||||
|
('attribution', models.OneToOneField(related_name='revente',
|
||||||
|
to='bda.Attribution')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Revente',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='participant',
|
||||||
|
name='choicesrevente',
|
||||||
|
field=models.ManyToManyField(related_name='revente',
|
||||||
|
to='bda.Spectacle', blank=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='spectaclerevente',
|
||||||
|
name='interested',
|
||||||
|
field=models.ManyToManyField(related_name='wanted',
|
||||||
|
to='bda.Participant', blank=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='spectaclerevente',
|
||||||
|
name='seller',
|
||||||
|
field=models.ForeignKey(related_name='original_shows',
|
||||||
|
verbose_name='Vendeur',
|
||||||
|
to='bda.Participant'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='spectaclerevente',
|
||||||
|
name='soldTo',
|
||||||
|
field=models.ForeignKey(verbose_name='Vendue \xe0', blank=True,
|
||||||
|
to='bda.Participant', null=True),
|
||||||
|
),
|
||||||
|
]
|
130
bda/models.py
130
bda/models.py
|
@ -5,6 +5,8 @@ from __future__ import print_function
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import calendar
|
import calendar
|
||||||
|
import random
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
@ -158,6 +160,9 @@ class Participant(models.Model):
|
||||||
max_length=6, choices=PAYMENT_TYPES,
|
max_length=6, choices=PAYMENT_TYPES,
|
||||||
blank=True)
|
blank=True)
|
||||||
tirage = models.ForeignKey(Tirage)
|
tirage = models.ForeignKey(Tirage)
|
||||||
|
choicesrevente = models.ManyToManyField(Spectacle,
|
||||||
|
related_name="subscribed",
|
||||||
|
blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s - %s" % (self.user, self.tirage.title)
|
return "%s - %s" % (self.user, self.tirage.title)
|
||||||
|
@ -205,4 +210,127 @@ class Attribution(models.Model):
|
||||||
given = models.BooleanField("Donnée", default=False)
|
given = models.BooleanField("Donnée", default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s -- %s" % (self.participant, self.spectacle)
|
return "%s -- %s, %s" % (self.participant.user, self.spectacle.title,
|
||||||
|
self.spectacle.date)
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class SpectacleRevente(models.Model):
|
||||||
|
attribution = models.OneToOneField(Attribution,
|
||||||
|
related_name="revente")
|
||||||
|
date = models.DateTimeField("Date de mise en vente",
|
||||||
|
default=timezone.now)
|
||||||
|
answered_mail = models.ManyToManyField(Participant,
|
||||||
|
related_name="wanted",
|
||||||
|
blank=True)
|
||||||
|
seller = models.ForeignKey(Participant,
|
||||||
|
related_name="original_shows",
|
||||||
|
verbose_name="Vendeur")
|
||||||
|
soldTo = models.ForeignKey(Participant, blank=True, null=True,
|
||||||
|
verbose_name="Vendue à")
|
||||||
|
|
||||||
|
notif_sent = models.BooleanField("Notification envoyée",
|
||||||
|
default=False)
|
||||||
|
tirage_done = models.BooleanField("Tirage effectué",
|
||||||
|
default=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def expiration_time(self):
|
||||||
|
# L'acheteur doit être connu au plus 12h avant le spectacle
|
||||||
|
remaining_time = (self.attribution.spectacle.date
|
||||||
|
- self.date - timedelta(hours=13))
|
||||||
|
# Au minimum, on attend 2 jours avant le tirage
|
||||||
|
delay = min(remaining_time, timedelta(days=2))
|
||||||
|
# On a aussi 1h pour changer d'avis
|
||||||
|
return self.date + delay + timedelta(hours=1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shotgun(self):
|
||||||
|
# Soit on a dépassé le délai du tirage, soit il reste peu de
|
||||||
|
# temps avant le spectacle
|
||||||
|
# On se laisse 5min de marge pour cron
|
||||||
|
return (timezone.now() > self.expiration_time + timedelta(minutes=5) or
|
||||||
|
(self.attribution.spectacle.date <= timezone.now() +
|
||||||
|
timedelta(days=1))) and (timezone.now() >= self.date +
|
||||||
|
timedelta(minutes=15))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s -- %s" % (self.seller,
|
||||||
|
self.attribution.spectacle.title)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Revente"
|
||||||
|
|
||||||
|
def send_notif(self):
|
||||||
|
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
||||||
|
|
||||||
|
mails_to_send = []
|
||||||
|
mail_object = "%s" % (self.attribution.spectacle)
|
||||||
|
for participant in inscrits:
|
||||||
|
mail_body = render_template('mail-revente.txt', {
|
||||||
|
'user': participant.user,
|
||||||
|
'spectacle': self.spectacle,
|
||||||
|
'revente': self})
|
||||||
|
mail_tot = mail.EmailMessage(
|
||||||
|
mail_object, mail_body,
|
||||||
|
settings.REVENTE_FROM, [participant.user.email],
|
||||||
|
[], headers={'Reply-To': settings.REVENTE_REPLY_TO})
|
||||||
|
mails_to_send.append(mail_tot)
|
||||||
|
|
||||||
|
connection = mail.get_connection()
|
||||||
|
connection.send_messages(mails_to_send)
|
||||||
|
self.notif_sent = True
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def mail_shotgun(self):
|
||||||
|
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
||||||
|
|
||||||
|
mails_to_send = []
|
||||||
|
mail_object = "%s" % (self.attribution.spectacle)
|
||||||
|
for participant in inscrits:
|
||||||
|
mail_body = render_template('mail-shotgun.txt', {
|
||||||
|
'user': participant.user,
|
||||||
|
'spectacle': self.spectacle,
|
||||||
|
'mail': self.attribution.participant.user.email})
|
||||||
|
mail_tot = mail.EmailMessage(
|
||||||
|
mail_object, mail_body,
|
||||||
|
settings.REVENTE_FROM, [participant.user.email],
|
||||||
|
[], headers={'Reply-To': settings.REVENTE_REPLY_TO})
|
||||||
|
mails_to_send.append(mail_tot)
|
||||||
|
|
||||||
|
connection = mail.get_connection()
|
||||||
|
connection.send_messages(mails_to_send)
|
||||||
|
self.notif_sent = True
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def tirage(self):
|
||||||
|
inscrits = self.answered_mail
|
||||||
|
spectacle = self.attribution.spectacle
|
||||||
|
seller = self.seller
|
||||||
|
if inscrits.exists():
|
||||||
|
winner = random.choice(inscrits.all())
|
||||||
|
self.soldTo = winner
|
||||||
|
mail_buyer = """Bonjour,
|
||||||
|
|
||||||
|
Tu as été tiré-e au sort pour racheter une place pour %s le %s (%s) à %0.02f€.
|
||||||
|
Tu peux contacter le vendeur à l'adresse %s.
|
||||||
|
|
||||||
|
Chaleureusement,
|
||||||
|
Le BdA""" % (spectacle.title, spectacle.date_no_seconds(),
|
||||||
|
spectacle.location, spectacle.price, seller.email)
|
||||||
|
|
||||||
|
mail.send_mail("BdA-Revente : %s" % spectacle.title,
|
||||||
|
mail_buyer, "bda@ens.fr", [winner.user.email],
|
||||||
|
fail_silently=False)
|
||||||
|
mail_seller = """Bonjour,
|
||||||
|
La personne tirée au sort pour racheter ta place pour %s est %s.
|
||||||
|
Tu peux le/la contacter à l'adresse %s.
|
||||||
|
|
||||||
|
Chaleureusement,
|
||||||
|
Le BdA""" % (spectacle.title, winner.user.get_full_name(), winner.user.email)
|
||||||
|
|
||||||
|
mail.send_mail("BdA-Revente : %s" % spectacle.title,
|
||||||
|
mail_seller, "bda@ens.fr", [seller.email],
|
||||||
|
fail_silently=False)
|
||||||
|
self.tirage_done = True
|
||||||
|
self.save()
|
||||||
|
|
9
bda/templates/bda-interested.html
Normal file
9
bda/templates/bda-interested.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
<h2>Inscription à une revente</h2>
|
||||||
|
<p class="success"> Votre inscription pour a bien été enregistrée !</p>
|
||||||
|
<p>Le tirage au sort pour cette revente ({{spectacle}}) sera effectué le {{date}}.
|
||||||
|
|
||||||
|
{% endblock %}
|
6
bda/templates/bda-no-revente.html
Normal file
6
bda/templates/bda-no-revente.html
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
<h2>BdA-Revente</h2>
|
||||||
|
<p>Il n'y a plus de places en revente pour ce spectacle, désolé !</p>
|
||||||
|
{% endblock %}
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
<h2><strong>Nope</strong></h1>
|
<h2><strong>Nope</strong></h2>
|
||||||
<p>Avant de revendre des places, il faut aller les payer !</p>
|
<p>Avant de revendre des places, il faut aller les payer !</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,26 +1,73 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load bootstrap %}
|
||||||
|
|
||||||
{% block extra_head %}
|
|
||||||
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
|
|
||||||
<h2>Revente de place</h1>
|
<h2>Revente de place</h2>
|
||||||
<form action="" method="post" id="resellform">
|
<h3>Places non revendues</h3>
|
||||||
|
<form class="form-horizontal" action="" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if form.spectacle.errors %}<ul class="errorlist"><li>Sélectionnez un spetacle</li></ul>{% endif %}
|
<div class="form-group">
|
||||||
<p>
|
<div class="multiple-checkbox">
|
||||||
<pre>
|
<ul>
|
||||||
Bonjour,<br />
|
{% for box in resellform.attributions %}
|
||||||
<br />
|
<li>
|
||||||
Je souhaite revendre {{ form.count }} place(s) pour {{ form.spectacle }}.<br />
|
{{box.tag}}
|
||||||
Contactez-moi par email si vous êtes intéressé !<br />
|
{{box.choice_label}}
|
||||||
<br />
|
</li>
|
||||||
{{ user.get_full_name }} ({{ user.email }})
|
{% endfor %}
|
||||||
</pre>
|
</ul>
|
||||||
</p>
|
</div>
|
||||||
<input class="btn btn-primary" type="submit" value="Envoyer" />
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<br>
|
||||||
|
{% if annulform.attributions or overdue %}
|
||||||
|
<h3>Places en cours de revente</h3>
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="multiple-checkbox">
|
||||||
|
<ul>
|
||||||
|
{% for box in annulform.attributions %}
|
||||||
|
<li>
|
||||||
|
{{box.tag}}
|
||||||
|
{{box.choice_label}}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% for attrib in overdue %}
|
||||||
|
<li>
|
||||||
|
<input type="checkbox" style="visibility:hidden">
|
||||||
|
{{attrib.spectacle}}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if annulform.attributions %}
|
||||||
|
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
{% if sold %}
|
||||||
|
<h3>Places revendues</h3>
|
||||||
|
<table class="table">
|
||||||
|
{% for attrib in sold %}
|
||||||
|
<tr>
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<td>{{attrib.spectacle}}</td>
|
||||||
|
<td>{{attrib.revente.soldTo.user.get_full_name}}</td>
|
||||||
|
<td><button type="submit" class="btn btn-primary" name="transfer"
|
||||||
|
value={{attrib.revente.id}}>Transférer</button></td>
|
||||||
|
<td><button type="submit" class="btn btn-primary" name="reinit"
|
||||||
|
value={{attrib.revente.id}}>Réinitialiser</button></td>
|
||||||
|
</form>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
|
|
||||||
{% block extra_head %}
|
|
||||||
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
|
|
||||||
<h1>Revente de place</h1>
|
<h2>Revente de place</h2>
|
||||||
<p class="success">Votre offre de revente de {{ places }} pour {{ show.title }} le {{ show.date_no_seconds }} ({{ show.location }}) à {{ show.price }}€ a bien été envoyée.</p>
|
<p class="success">Un mail a bien été envoyé à {{seller.get_full_name}} ({{seller.email}}), pour racheter une place pour {{spectacle.title}} !</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
6
bda/templates/bda-wrongtime.html
Normal file
6
bda/templates/bda-wrongtime.html
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
<h2>Nope</h2>
|
||||||
|
<p>Cette revente n'est pas disponible actuellement, désolé !</p>
|
||||||
|
{% endblock %}
|
23
bda/templates/liste-reventes.html
Normal file
23
bda/templates/liste-reventes.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
{% load bootstrap %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
<h2>Inscriptions pour BDA-Revente</h2>
|
||||||
|
{% if deja_revente %}
|
||||||
|
<p class="success">Des reventes existent déjà pour certains de ces spectacles ; vérifie les places disponibles sans tirage !</p>
|
||||||
|
{% endif %}
|
||||||
|
<form action="" class="form-horizontal" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{form | bootstrap}}
|
||||||
|
<input type="submit" class="btn btn-primary" value="S'inscrire pour les places sélectionnées">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% if shotgun %}
|
||||||
|
<br>
|
||||||
|
<h3>Places disponibles immédiatement</h3>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
{% for spectacle in shotgun %}
|
||||||
|
<li><a href="{% url "bda-buy-revente" spectacle.id %}">{{spectacle}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
8
bda/templates/mail-revente.txt
Normal file
8
bda/templates/mail-revente.txt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Bonjour {{ user.get_full_name }}
|
||||||
|
|
||||||
|
Une place pour le spectacle {{ spectacle.title }} ({{spectacle.date_no_seconds}}) a été postée sur BdA-Revente.
|
||||||
|
|
||||||
|
Si ce spectacle t'intéresse toujours, merci de nous le signaler en cliquant sur ce lien : {% url "bda-interested-revente" revente.id %}. Dans le cas où plusieurs personnes seraient intéressées, nous procèderons à un tirage au sort le {{revente.date_no_seconds}}
|
||||||
|
|
||||||
|
Chaleureusement,
|
||||||
|
Le BdA
|
8
bda/templates/mail-shotgun.txt
Normal file
8
bda/templates/mail-shotgun.txt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Bonjour {{ user.get_full_name }}
|
||||||
|
|
||||||
|
Une place pour le spectacle {{ spectacle.title }} ({{spectacle.date_no_seconds}}) a été postée sur BdA-Revente.
|
||||||
|
|
||||||
|
Puisque ce spectacle a lieu dans moins de 24h, il n'y a pas de tirage au sort pour cette place : elle est disponible immédiatement à l'addresse {{url "bda-buy-revente" spectacle.id}}, à la disposition de tous.
|
||||||
|
|
||||||
|
Chaleureusement,
|
||||||
|
Le BdA
|
20
bda/templates/revente-confirm.html
Normal file
20
bda/templates/revente-confirm.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
|
||||||
|
{%block realcontent %}
|
||||||
|
<h2>Rachat d'une place</h2>
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<pre>
|
||||||
|
Bonjour !
|
||||||
|
|
||||||
|
Je souhaiterais racheter ta place pour {{spectacle.title}} le {{spectacle.date}} ({{spectacle.location}}) à {{spectacle.price}}€.
|
||||||
|
Contacte-moi si tu es toujours intéressé-e !
|
||||||
|
|
||||||
|
{{user.get_full_name}} ({{user.email}})
|
||||||
|
</pre>
|
||||||
|
<input type="submit" class="btn btn-primary pull-right" value="Envoyer">
|
||||||
|
</form>
|
||||||
|
<p class="bda-prix">Note : ce mail sera envoyé à une personne au hasard revendant sa place.</p>
|
||||||
|
{%endblock%}
|
|
@ -32,6 +32,15 @@ urlpatterns = [
|
||||||
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
|
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
|
||||||
views.unpaid,
|
views.unpaid,
|
||||||
name="bda-unpaid"),
|
name="bda-unpaid"),
|
||||||
|
url(r'^liste-revente/(?P<tirage_id>\d+)$',
|
||||||
|
"bda.views.list_revente",
|
||||||
|
name="bda-liste-revente"),
|
||||||
|
url(r'^buy-revente/(?P<spectacle_id>\d+)$',
|
||||||
|
"bda.views.buy_revente",
|
||||||
|
name="bda-buy-revente"),
|
||||||
|
url(r'^revente-interested/(?P<revente_id>\d+)$',
|
||||||
|
"bda.views.revente_interested",
|
||||||
|
name='bda-revente-interested'),
|
||||||
url(r'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel),
|
url(r'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel),
|
||||||
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
||||||
name='bda-descriptions'),
|
name='bda-descriptions'),
|
||||||
|
|
201
bda/views.py
201
bda/views.py
|
@ -4,13 +4,16 @@ from __future__ import division
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Count
|
from django.db.models import Count, Q
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
from django.forms.models import inlineformset_factory
|
from django.forms.models import inlineformset_factory
|
||||||
from django.http import HttpResponseBadRequest
|
from django.http import HttpResponseBadRequest, HttpResponseRedirect
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
|
@ -18,13 +21,15 @@ from django.utils import timezone
|
||||||
from django.views.generic.list import ListView
|
from django.views.generic.list import ListView
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from gestioncof.decorators import cof_required, buro_required
|
from gestioncof.decorators import cof_required, buro_required
|
||||||
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
|
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
|
||||||
Tirage, render_template
|
Tirage, render_template, SpectacleRevente
|
||||||
from bda.algorithm import Algorithm
|
from bda.algorithm import Algorithm
|
||||||
|
|
||||||
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm
|
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\
|
||||||
|
InscriptionReventeForm
|
||||||
|
|
||||||
|
|
||||||
@cof_required
|
@cof_required
|
||||||
|
@ -231,6 +236,11 @@ def do_tirage(request, tirage_id):
|
||||||
Attribution(spectacle=show, participant=member)
|
Attribution(spectacle=show, participant=member)
|
||||||
for show, members, _ in results
|
for show, members, _ in results
|
||||||
for member, _, _, _ in members])
|
for member, _, _, _ in members])
|
||||||
|
# On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues
|
||||||
|
for (show, _, losers) in results:
|
||||||
|
for (loser, _, _, _) in losers:
|
||||||
|
loser.choicesrevente.add(show)
|
||||||
|
loser.save()
|
||||||
return render(request, "bda-attrib-extra.html", data)
|
return render(request, "bda-attrib-extra.html", data)
|
||||||
else:
|
else:
|
||||||
return render(request, "bda-attrib.html", data)
|
return render(request, "bda-attrib.html", data)
|
||||||
|
@ -277,14 +287,185 @@ def revente(request, tirage_id):
|
||||||
user=request.user, tirage=tirage)
|
user=request.user, tirage=tirage)
|
||||||
if not participant.paid:
|
if not participant.paid:
|
||||||
return render(request, "bda-notpaid.html", {})
|
return render(request, "bda-notpaid.html", {})
|
||||||
if request.POST:
|
if request.method == 'POST':
|
||||||
form = ResellForm(participant, request.POST)
|
if 'resell' in request.POST:
|
||||||
if form.is_valid():
|
resellform = ResellForm(participant, request.POST, prefix='resell')
|
||||||
return do_resell(request, form)
|
annulform = AnnulForm(participant, prefix='annul')
|
||||||
|
if resellform.is_valid():
|
||||||
|
attributions = resellform.cleaned_data["attributions"]
|
||||||
|
for attribution in attributions:
|
||||||
|
revente, created = SpectacleRevente.objects.get_or_create(
|
||||||
|
attribution=attribution,
|
||||||
|
defaults={'seller': participant})
|
||||||
|
if not created:
|
||||||
|
revente.seller = participant
|
||||||
|
revente.date = timezone.now()
|
||||||
|
revente.save()
|
||||||
|
|
||||||
|
elif 'annul' in request.POST:
|
||||||
|
annulform = AnnulForm(participant, request.POST, prefix='annul')
|
||||||
|
resellform = ResellForm(participant, prefix='resell')
|
||||||
|
if annulform.is_valid():
|
||||||
|
attributions = annulform.cleaned_data["attributions"]
|
||||||
|
for attribution in attributions:
|
||||||
|
attribution.revente.delete()
|
||||||
|
|
||||||
|
elif 'transfer' in request.POST:
|
||||||
|
resellform = ResellForm(participant, prefix='resell')
|
||||||
|
annulform = AnnulForm(participant, prefix='annul')
|
||||||
|
|
||||||
|
revente_id = request.POST['transfer'][0]
|
||||||
|
rev = SpectacleRevente.objects.filter(soldTo__isnull=False,
|
||||||
|
id=revente_id)
|
||||||
|
if rev.exists():
|
||||||
|
revente = rev.get()
|
||||||
|
attrib = revente.attribution
|
||||||
|
attrib.participant = revente.soldTo
|
||||||
|
attrib.save()
|
||||||
|
|
||||||
|
elif 'reinit' in request.POST:
|
||||||
|
resellform = ResellForm(participant, prefix='resell')
|
||||||
|
annulform = AnnulForm(participant, prefix='annul')
|
||||||
|
revente_id = request.POST['reinit'][0]
|
||||||
|
rev = SpectacleRevente.objects.filter(soldTo__isnull=False,
|
||||||
|
id=revente_id)
|
||||||
|
if rev.exists():
|
||||||
|
revente = rev.get()
|
||||||
|
revente.date = timezone.now() - timedelta(hours=1)
|
||||||
|
revente.soldTo = None
|
||||||
|
revente.answered_mail = None
|
||||||
|
|
||||||
|
else:
|
||||||
|
resellform = ResellForm(participant, prefix='resell')
|
||||||
|
annulform = AnnulForm(participant, prefix='annul')
|
||||||
else:
|
else:
|
||||||
form = ResellForm(participant)
|
resellform = ResellForm(participant, prefix='resell')
|
||||||
|
annulform = AnnulForm(participant, prefix='annul')
|
||||||
|
|
||||||
|
overdue = participant.attribution_set.filter(
|
||||||
|
spectacle__date__gte=timezone.now(),
|
||||||
|
revente__isnull=False,
|
||||||
|
revente__seller=participant,
|
||||||
|
revente__date__lte=timezone.now()-timedelta(hours=1)).filter(
|
||||||
|
Q(revente__soldTo__isnull=True) | Q(revente__soldTo=participant))
|
||||||
|
sold = participant.attribution_set.filter(
|
||||||
|
spectacle__date__gte=timezone.now(),
|
||||||
|
revente__isnull=False,
|
||||||
|
revente__soldTo__isnull=False).exclude(
|
||||||
|
revente__soldTo=participant)
|
||||||
|
|
||||||
return render(request, "bda-revente.html",
|
return render(request, "bda-revente.html",
|
||||||
{"form": form, 'tirage': tirage})
|
{'tirage': tirage, 'overdue': overdue, "sold": sold,
|
||||||
|
"annulform": annulform, "resellform": resellform})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def revente_interested(request, revente_id):
|
||||||
|
revente = get_object_or_404(SpectacleRevente, id=revente_id)
|
||||||
|
participant, created = Participant.objects.get_or_create(
|
||||||
|
user=request.user, tirage=revente.attribution.spectacle.tirage)
|
||||||
|
if timezone.now() < revente.date + timedelta(hours=1) or revente.shotgun:
|
||||||
|
return render(request, "bda-wrongtime.html", {})
|
||||||
|
|
||||||
|
revente.answered_mail.add(participant)
|
||||||
|
return render(request, "bda-interested.html",
|
||||||
|
{"spectacle": revente.attribution.spectacle,
|
||||||
|
"date": revente.expiration_time})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def list_revente(request, tirage_id):
|
||||||
|
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||||
|
participant, created = Participant.objects.get_or_create(
|
||||||
|
user=request.user, tirage=tirage)
|
||||||
|
spectacles = tirage.spectacle_set.filter(
|
||||||
|
date__gte=timezone.now())
|
||||||
|
shotgun = []
|
||||||
|
deja_revente = False
|
||||||
|
for spectacle in spectacles:
|
||||||
|
revente_objects = SpectacleRevente.objects.filter(
|
||||||
|
attribution__spectacle=spectacle,
|
||||||
|
soldTo__isnull=True)
|
||||||
|
revente_count = 0
|
||||||
|
for revente in revente_objects:
|
||||||
|
if revente.shotgun:
|
||||||
|
revente_count += 1
|
||||||
|
if revente_count:
|
||||||
|
spectacle.revente_count = revente_count
|
||||||
|
shotgun.append(spectacle)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = InscriptionReventeForm(tirage, request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
choices = form.cleaned_data['spectacles']
|
||||||
|
participant.choicesrevente = choices
|
||||||
|
participant.save()
|
||||||
|
for spectacle in choices:
|
||||||
|
qset = SpectacleRevente.objects.filter(
|
||||||
|
attribution__spectacle=spectacle)
|
||||||
|
if qset.exists():
|
||||||
|
# On l'inscrit à l'un des tirages au sort
|
||||||
|
for revente in qset.all():
|
||||||
|
if revente.shotgun and not revente.soldTo:
|
||||||
|
deja_revente = True
|
||||||
|
else:
|
||||||
|
revente.answered_mail.add(participant)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
form = InscriptionReventeForm(
|
||||||
|
tirage,
|
||||||
|
initial={'spectacles': participant.choicesrevente.all()})
|
||||||
|
|
||||||
|
return render(request, "liste-reventes.html",
|
||||||
|
{"form": form, 'shotgun': shotgun,
|
||||||
|
"deja_revente": deja_revente})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def buy_revente(request, spectacle_id):
|
||||||
|
spectacle = get_object_or_404(Spectacle, id=spectacle_id)
|
||||||
|
tirage = spectacle.tirage
|
||||||
|
participant, created = Participant.objects.get_or_create(
|
||||||
|
user=request.user, tirage=tirage)
|
||||||
|
reventes = SpectacleRevente.objects.filter(
|
||||||
|
attribution__spectacle=spectacle,
|
||||||
|
soldTo__isnull=True)
|
||||||
|
if reventes.filter(seller=participant).exists():
|
||||||
|
revente = reventes.filter(seller=participant)[0]
|
||||||
|
revente.delete()
|
||||||
|
return HttpResponseRedirect(reverse("bda-liste-revente",
|
||||||
|
args=[tirage.id]))
|
||||||
|
reventes_shotgun = []
|
||||||
|
for revente in reventes.all():
|
||||||
|
if revente.shotgun:
|
||||||
|
reventes_shotgun.append(revente)
|
||||||
|
|
||||||
|
if reventes_shotgun.empty():
|
||||||
|
return render(request, "bda-no-revente.html", {})
|
||||||
|
|
||||||
|
if request.POST:
|
||||||
|
revente = random.choice(reventes_shotgun)
|
||||||
|
revente.soldTo = participant
|
||||||
|
revente.save()
|
||||||
|
mail = """Bonjour !
|
||||||
|
|
||||||
|
Je souhaiterais racheter ta place pour %s le %s (%s) à %.02f€.
|
||||||
|
Contacte-moi si tu es toujours intéressé·e !
|
||||||
|
|
||||||
|
%s (%s)""" % (spectacle.title, spectacle.date_no_seconds(),
|
||||||
|
spectacle.location, spectacle.price,
|
||||||
|
request.user.get_full_name(), request.user.email)
|
||||||
|
send_mail("BdA-Revente : %s" % spectacle.title, mail,
|
||||||
|
request.user.email,
|
||||||
|
[revente.seller.user.email],
|
||||||
|
fail_silently=False)
|
||||||
|
return render(request, "bda-success.html",
|
||||||
|
{"seller": revente.attribution.participant.user,
|
||||||
|
"spectacle": spectacle})
|
||||||
|
|
||||||
|
return render(request, "revente-confirm.html",
|
||||||
|
{"spectacle": spectacle,
|
||||||
|
"user": request.user})
|
||||||
|
|
||||||
|
|
||||||
@buro_required
|
@buro_required
|
||||||
|
|
|
@ -152,6 +152,9 @@ PETITS_COURS_REPLYTO = "cof@ens.fr"
|
||||||
RAPPEL_FROM = 'Le BdA <bda@ens.fr>'
|
RAPPEL_FROM = 'Le BdA <bda@ens.fr>'
|
||||||
RAPPEL_REPLY_TO = RAPPEL_FROM
|
RAPPEL_REPLY_TO = RAPPEL_FROM
|
||||||
|
|
||||||
|
REVENTE_FROM = 'BDA-Revente <bda-revente@ens.fr>'
|
||||||
|
REVENTE_REPLY_TO = REVENTE_FROM
|
||||||
|
|
||||||
LOGIN_URL = "/gestion/login"
|
LOGIN_URL = "/gestion/login"
|
||||||
LOGIN_REDIRECT_URL = "/gestion/"
|
LOGIN_REDIRECT_URL = "/gestion/"
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{% url "bda-places-attribuees" tirage.id %}">Mes places</a></li>
|
<li><a href="{% url "bda-places-attribuees" tirage.id %}">Mes places</a></li>
|
||||||
<li><a href="{% url "bda-revente" tirage.id %}">Revendre une place</a></li>
|
<li><a href="{% url "bda-revente" tirage.id %}">Revendre une place</a></li>
|
||||||
|
<li><a href="{% url "bda-liste-revente" tirage.id %}">S'inscrire à BDA-Revente</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -7,3 +7,4 @@ DBNAME="cof_gestion"
|
||||||
DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
||||||
|
|
||||||
19 */12 * * * date >> /vagrant/rappels.log ; python /vagrant/manage.py sendrappels >> /vagrant/rappels.log 2>&1
|
19 */12 * * * date >> /vagrant/rappels.log ; python /vagrant/manage.py sendrappels >> /vagrant/rappels.log 2>&1
|
||||||
|
*/5 * * * * python /vagrant/manage.py manage_revente >> /vagrant/reventes.log 2>&1
|
||||||
|
|
|
@ -14,3 +14,14 @@ envoyés).
|
||||||
- Garde les logs peut être une bonne idée.
|
- Garde les logs peut être une bonne idée.
|
||||||
|
|
||||||
Exemple : voir le fichier `provisioning/cron.dev`.
|
Exemple : voir le fichier `provisioning/cron.dev`.
|
||||||
|
|
||||||
|
## Gestion des mails de revente
|
||||||
|
|
||||||
|
Il faut effectuer très régulièrement la commande `manage_reventes` de GestioCOF,
|
||||||
|
qui gère toutes les actions associées à BdA-Revente : envoi des mails de notification,
|
||||||
|
tirages.
|
||||||
|
|
||||||
|
- Pour l'instant un délai de 5 min est hardcodé
|
||||||
|
- Garde des logs ; ils vont finir par être assez lourds si on a beaucoup de reventes.
|
||||||
|
|
||||||
|
Exemple : provisioning/cron.dev
|
||||||
|
|
Loading…
Reference in a new issue