diff --git a/dav_event_office/templates/dav_event_office/event_detail.html b/dav_event_office/templates/dav_event_office/event_detail.html index 3ab06c4..fa4c9e2 100644 --- a/dav_event_office/templates/dav_event_office/event_detail.html +++ b/dav_event_office/templates/dav_event_office/event_detail.html @@ -210,6 +210,17 @@
{% csrf_token %} + {% if event.charge and has_permission_payment %} + +   + {% endif %} {% if has_permission_update_participants %} {% elif event.charge and participant.paid %}   - - {% if participant.apply_reduced_fee %}%{% else %} {% endif %}{% bootstrap_icon 'piggy-bank' %} - + %{% bootstrap_icon 'piggy-bank' %} {% elif event.charge and has_permission_payment %}   - {% if participant.apply_reduced_fee %}%{% elif participant.created_at|date:'U' < '1608764400' %}? {% else %} {% endif %} {% elif event.charge %}   - - {% if participant.apply_reduced_fee %}%{% elif participant.created_at|date:'U' < '1608764400' %}? {% else %} {% endif %}{% bootstrap_icon 'piggy-bank' %} - + {% if participant.apply_reduced_fee %}%{% else %}? {% endif %}{% bootstrap_icon 'piggy-bank' %} {% else %} - + {% endif %}
@@ -287,6 +303,11 @@ {% endif %} +
+
+ {% trans 'Gebuchte Teilnahmegebühren' %}: {{ earnings|floatformat:2 }} € +
+
{% if participant_emails %}
@@ -380,14 +401,16 @@ {% if event.charge and participant.paid %}   - - {% if participant.apply_reduced_fee %}%{% else %} {% endif %}{% bootstrap_icon 'piggy-bank' %} - + %{% bootstrap_icon 'piggy-bank' %} {% elif event.charge %}   - - {% if participant.apply_reduced_fee %}%{% else %} {% endif %}{% bootstrap_icon 'piggy-bank' %} - + %{% bootstrap_icon 'piggy-bank' %} {% endif %}
diff --git a/dav_event_office/templates/dav_event_office/participant_list.html b/dav_event_office/templates/dav_event_office/participant_list.html index 99c2439..c9a9e71 100644 --- a/dav_event_office/templates/dav_event_office/participant_list.html +++ b/dav_event_office/templates/dav_event_office/participant_list.html @@ -71,24 +71,37 @@
{% csrf_token %} + {% if event.charge %} + +   + {% endif %} {% if event.charge and participant.paid %} - {% if participant.apply_reduced_fee %}%{% else %} {% endif %}   ({{ event.charge|floatformat:'-2' }}{% if participant.apply_reduced_fee %} / 2{% endif %} €) {% elif event.charge %} - {% if participant.apply_reduced_fee %}%{% elif participant.created_at|date:'U' < '1608764400' %}? {% else %} {% endif %}   ({{ event.charge|floatformat:'-2' }}{% if participant.apply_reduced_fee %} / 2{% endif %} €) {% else %} - - {% bootstrap_icon 'piggy-bank' %} - + {% endif %}
diff --git a/dav_event_office/views.py b/dav_event_office/views.py index c44a136..148d3b1 100644 --- a/dav_event_office/views.py +++ b/dav_event_office/views.py @@ -53,6 +53,11 @@ class ParticipantListView(generic.ListView): participant = get_object_or_404(Participant, pk=participant_id) participant.paid = False participant.save() + elif action == 'toggle_reduced_fee': + participant_id = request.POST.get('id') + participant = get_object_or_404(Participant, pk=participant_id) + participant.apply_reduced_fee = not participant.apply_reduced_fee + participant.save() else: messages.error(request, 'unsupported action: {}'.format(action)) return HttpResponseRedirect(reverse('dav_event_office:participant-list')) diff --git a/dav_events/forms/__init__.py b/dav_events/forms/__init__.py index a13db64..c7f2392 100644 --- a/dav_events/forms/__init__.py +++ b/dav_events/forms/__init__.py @@ -1,3 +1,4 @@ from . import generic from . import events from . import participant +from . import registration diff --git a/dav_events/forms/participant.py b/dav_events/forms/participant.py index 92dd23c..e471173 100644 --- a/dav_events/forms/participant.py +++ b/dav_events/forms/participant.py @@ -7,7 +7,9 @@ from ..models import Participant class ParticipantForm(forms.ModelForm): class Meta: model = Participant - exclude = ['event', 'created_at', 'position', 'purge_at'] + exclude = ['event', 'created_at', 'position', + 'privacy_policy', 'privacy_policy_accepted', + 'paid', 'purge_at'] widgets = { 'emergency_contact': forms.Textarea(attrs={'rows': 4}), 'experience': forms.Textarea(attrs={'rows': 5}), diff --git a/dav_events/forms/registration.py b/dav_events/forms/registration.py new file mode 100644 index 0000000..2d28b2b --- /dev/null +++ b/dav_events/forms/registration.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +from django import forms +from django.utils.translation import ugettext_lazy as _ + + +class RegistrationResponseForm(forms.Form): + apply_reduced_fee = forms.BooleanField(required=False, + label=_(u'Reduzierte Teilnahmegebühr')) diff --git a/dav_events/migrations/0039_auto_20201215_1155.py b/dav_events/migrations/0039_auto_20201215_1155.py new file mode 100644 index 0000000..42d664c --- /dev/null +++ b/dav_events/migrations/0039_auto_20201215_1155.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.29 on 2020-12-15 10:55 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dav_events', '0038_auto_20201209_1542'), + ] + + operations = [ + migrations.AlterField( + model_name='participant', + name='apply_reduced_fee', + field=models.BooleanField(default=False, help_text='Für Jugendliche und Junioren (bis zum vollendeten 25. Lebensjahr), sowie Mitglieder mit geringen finanziellen Mitteln (Nachweis durch "Karlsruher Pass"), wird die Teilnahmegebühr auf 50% ermäßigt.', verbose_name='Antrag auf reduzierte Teilnahmegebühr'), + ), + migrations.AlterField( + model_name='participant', + name='year_of_birth', + field=models.IntegerField(help_text='Vierstellige Jahreszahl', verbose_name='Geburtsjahr'), + ), + migrations.AlterField( + model_name='trashedparticipant', + name='apply_reduced_fee', + field=models.BooleanField(default=False, help_text='Für Jugendliche und Junioren (bis zum vollendeten 25. Lebensjahr), sowie Mitglieder mit geringen finanziellen Mitteln (Nachweis durch "Karlsruher Pass"), wird die Teilnahmegebühr auf 50% ermäßigt.', verbose_name='Antrag auf reduzierte Teilnahmegebühr'), + ), + migrations.AlterField( + model_name='trashedparticipant', + name='year_of_birth', + field=models.IntegerField(help_text='Vierstellige Jahreszahl', verbose_name='Geburtsjahr'), + ), + ] diff --git a/dav_events/migrations/0040_auto_20201216_1712.py b/dav_events/migrations/0040_auto_20201216_1712.py new file mode 100644 index 0000000..d844345 --- /dev/null +++ b/dav_events/migrations/0040_auto_20201216_1712.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.29 on 2020-12-16 16:12 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dav_events', '0039_auto_20201215_1155'), + ] + + operations = [ + migrations.AddField( + model_name='participant', + name='privacy_policy', + field=models.TextField(blank=True, verbose_name='Erklärung zur Datenspeicherung'), + ), + migrations.AddField( + model_name='participant', + name='privacy_policy_accepted', + field=models.BooleanField(default=False, verbose_name='Einwilligung zur Datenspeicherung'), + ), + migrations.AddField( + model_name='trashedparticipant', + name='privacy_policy', + field=models.TextField(blank=True, verbose_name='Erklärung zur Datenspeicherung'), + ), + migrations.AddField( + model_name='trashedparticipant', + name='privacy_policy_accepted', + field=models.BooleanField(default=False, verbose_name='Einwilligung zur Datenspeicherung'), + ), + ] diff --git a/dav_events/models/participant.py b/dav_events/models/participant.py index 01af818..da0b692 100644 --- a/dav_events/models/participant.py +++ b/dav_events/models/participant.py @@ -53,6 +53,11 @@ class AbstractParticipant(models.Model): verbose_name=_('Anmerkung'), help_text=_('Kann frei gelassen werden.')) + privacy_policy = models.TextField(blank=True, + verbose_name=_('Erklärung zur Datenspeicherung')) + privacy_policy_accepted = models.BooleanField(default=False, + verbose_name=_('Einwilligung zur Datenspeicherung')) + paid = models.BooleanField('Teilnehmerbeitrag bezahlt', default=False) purge_at = models.DateTimeField() diff --git a/dav_events/templates/dav_events/event_create/SummaryForm.html b/dav_events/templates/dav_events/event_create/SummaryForm.html index 4c925a4..7bf5704 100644 --- a/dav_events/templates/dav_events/event_create/SummaryForm.html +++ b/dav_events/templates/dav_events/event_create/SummaryForm.html @@ -40,11 +40,9 @@ {% bootstrap_icon 'remove' %}  {% trans 'Abbrechen' %} - {% endbuttons %} {% endblock form-buttons %} diff --git a/dav_events/templates/dav_events/event_registrations.html b/dav_events/templates/dav_events/event_registrations.html index b8b3278..531557e 100644 --- a/dav_events/templates/dav_events/event_registrations.html +++ b/dav_events/templates/dav_events/event_registrations.html @@ -272,10 +272,17 @@ Das musst du selbst (per E-Mail oder telefonisch) machen. {% csrf_token %} {% if has_permission_update_participants %} + {% if registration.apply_reduced_fee %} + + {% bootstrap_icon 'plus-sign' %} + + {% else %} + {% endif %}   {% elif event.charge and participant.paid %}   - - {% if participant.apply_reduced_fee %}%{% else %} {% endif %}{% bootstrap_icon 'piggy-bank' %} - + %{% bootstrap_icon 'piggy-bank' %} {% elif event.charge and has_permission_payment %}   - {% if participant.apply_reduced_fee %}%{% else %} {% endif %} {% elif event.charge %}   - - {% if participant.apply_reduced_fee %}%{% else %} {% endif %}{% bootstrap_icon 'piggy-bank' %} - + %{% bootstrap_icon 'piggy-bank' %} {% else %} - + {% endif %}
@@ -580,14 +592,16 @@ von Position {{ participant.position }} der Teilnehmerliste entfernt. {% if event.charge and participant.paid %}   - - {% if participant.apply_reduced_fee %}%{% else %} {% endif %}{% bootstrap_icon 'piggy-bank' %} - + %{% bootstrap_icon 'piggy-bank' %} {% elif event.charge %}   - - {% if participant.apply_reduced_fee %}%{% else %} {% endif %}{% bootstrap_icon 'piggy-bank' %} - + %{% bootstrap_icon 'piggy-bank' %} {% endif %} diff --git a/dav_events/templates/dav_events/registration_response.html b/dav_events/templates/dav_events/registration_response.html new file mode 100644 index 0000000..6f7bc83 --- /dev/null +++ b/dav_events/templates/dav_events/registration_response.html @@ -0,0 +1,45 @@ +{% extends 'dav_events/base.html' %} +{% load bootstrap3 %} +{% load i18n %} + +{% block head-title %} + {% trans 'Anmeldung' %} {{ registration.get_full_name }} - {{ registration.event.number }} - {{ block.super }} +{% endblock head-title %} + +{% block page-container %} +
+

+ Hallo {{ registration.event.trainer_firstname }}, +

+

+ du hast sicherlich schon gesehen, dass {{ registration.get_full_name }} angekreuzt hat, + die reduzierte Teilnahmegebühr zahlen zu wollen. +

+

+ Für Jugendliche und Junioren sowie Mitglieder mit geringen finanziellen Mitteln (Nachweis durch Karlsruher Paß) + wird die Teilnahmegebühr auf 50% ermäßigt. +

+

+ Wenn ihr bereits darüber gesprochen habt und ihr übereingekommen seid, + dass {{ registration.personal_names }} doch den vollen Betrag zahlen soll, + dann kannst du unten den Haken entfernen. +

+
+
+
+ {{ registration.event.number }} - {{ registration.event.title }}
+ {% trans 'Anmeldung' %} {{ registration.get_full_name }} +
+
+ {% csrf_token %} + {% bootstrap_form form %} + + + {% bootstrap_icon 'remove' %} Zurück + +
+
+{% endblock page-container %} diff --git a/dav_events/urls.py b/dav_events/urls.py index b348d56..40cb2d7 100644 --- a/dav_events/urls.py +++ b/dav_events/urls.py @@ -12,6 +12,7 @@ urlpatterns = [ views.events.EventUpdateStatusView.as_view(), name='updatestatus'), url(r'^(?P\d+)/edit', views.events.EventUpdateView.as_view(), name='update'), url(r'^(?P\d+)/', views.events.EventDetailView.as_view(), name='detail'), + url(r'^registration/(?P\d+)/', views.events.RespondRegistrationView.as_view(), name='respond_registration'), url(r'^action/(?P[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12})/', views.actions.OneClickActionRunView.as_view(), name='action_run'), ] diff --git a/dav_events/views/events.py b/dav_events/views/events.py index c234771..ee68873 100644 --- a/dav_events/views/events.py +++ b/dav_events/views/events.py @@ -2,6 +2,7 @@ import datetime import logging import os +from django.apps import apps from django.contrib import messages from django.contrib.auth import login from django.contrib.auth.decorators import login_required @@ -218,6 +219,16 @@ class EventRegistrationsView(EventPermissionMixin, generic.DetailView): participants_trash = event.trashed_participants.all() context['participants_trash'] = participants_trash + earnings = 0 + if event.charge: + for participant in participants: + if participant.paid: + if participant.apply_reduced_fee: + earnings += event.charge / 2 + else: + earnings += event.charge + context['earnings'] = earnings + if participants.count() > 1: email_list = [u'"{}" <{}>'.format(p.get_full_name(), p.email_address) for p in participants] email_list.sort() @@ -271,30 +282,13 @@ class EventRegistrationsView(EventPermissionMixin, generic.DetailView): messages.success(request, _(u'Der Anmeldeschluss wurde gelöscht')) def _accept_registration(self, request, registration): - event = registration.event + data = registration.get_data_dict() + del data['created_at'] + del data['answered_obsolete'] + data['position'] = registration.event.participants.count() + 1 - position = event.participants.count() + 1 - - data = { - 'event': event, - 'position': position, - 'personal_names': registration.personal_names, - 'family_names': registration.family_names, - 'address': registration.address, - 'postal_code': registration.postal_code, - 'city': registration.city, - 'email_address': registration.email_address, - 'year_of_birth': registration.year_of_birth, - 'apply_reduced_fee': registration.apply_reduced_fee, - 'phone_number': registration.phone_number, - 'dav_member': registration.dav_member, - 'dav_number': registration.dav_number, - 'emergency_contact': registration.emergency_contact, - 'experience': registration.experience, - 'note': registration.note, - 'purge_at': registration.purge_at, - } participant = models.Participant.objects.create(**data) + registration.status.set_accepted() messages.success(request, _(u'Teilnehmer hinzugefügt: {}'.format(participant.get_full_name()))) @@ -360,6 +354,12 @@ class EventRegistrationsView(EventPermissionMixin, generic.DetailView): self._reset_registration(registration) else: raise FieldDoesNotExist('Event has no registrations') + elif action == 'toggle_reduced_fee': + self.enforce_permission(event, permission='payment') + participant_id = request.POST.get('id') + participant = event.participants.get(id=participant_id) + participant.apply_reduced_fee = not participant.apply_reduced_fee + participant.save() elif action == 'confirm_payment': self.enforce_permission(event, permission='payment') participant_id = request.POST.get('id') @@ -455,6 +455,62 @@ class EventRegistrationsView(EventPermissionMixin, generic.DetailView): return super(EventRegistrationsView, self).dispatch(request, *args, **kwargs) +class RespondRegistrationView(EventPermissionMixin, generic.DetailView, generic.FormView): + permission = 'update-participants' + context_object_name = 'registration' + template_name = 'dav_events/registration_response.html' + form_class = forms.registration.RegistrationResponseForm + + def _accept_registration(self, request, registration): + data = registration.get_data_dict() + del data['created_at'] + del data['answered_obsolete'] + data['position'] = registration.event.participants.count() + 1 + + participant = models.Participant.objects.create(**data) + + registration.status.set_accepted() + messages.success(request, _(u'Teilnehmer hinzugefügt: {}'.format(participant.get_full_name()))) + + def has_permission(self, permission, obj): + user = self.request.user + return obj.event.workflow.has_permission(user, permission) + + def get_queryset(self): + model = apps.get_model(app_label='dav_registration', model_name='Registration') + return model.objects.all() + + def get_success_url(self): + return reverse('dav_events:registrations', args=[self.object.event.pk]) + + def get_initial(self): + return { + 'apply_reduced_fee': self.object.apply_reduced_fee, + } + + def form_valid(self, form): + registration = self.object + registration.apply_reduced_fee = form.cleaned_data['apply_reduced_fee'] + registration.save() + self._accept_registration(self.request, registration) + return HttpResponseRedirect(self.get_success_url()) + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + self.enforce_permission(self.object) + context = self.get_context_data(object=self.object) + return self.render_to_response(context) + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + self.enforce_permission(self.object) + return super(RespondRegistrationView, self).post(request, *args, **kwargs) + + @method_decorator(login_required) + def dispatch(self, request, *args, **kwargs): + return super(RespondRegistrationView, self).dispatch(request, *args, **kwargs) + + class EventUpdateStatusView(EventPermissionMixin, generic.DetailView): model = models.Event @@ -487,6 +543,18 @@ class EventUpdateStatusView(EventPermissionMixin, generic.DetailView): messages.error(request, message) return HttpResponseRedirect(event.get_absolute_url()) + if not event.workflow.has_reached_status('publishing*') and not event.workflow.has_reached_status('published*'): + cur_pub_date = event.planned_publication_date + real_pub_date, real_pub_issue = event.workflow.plan_publication(event.first_day, event.deadline) + if cur_pub_date != real_pub_date: + if real_pub_date is None: + real_pub_str = _(u'Unverzüglich') + else: + real_pub_str = u'%s (%s)' % (real_pub_date.strftime('%d.%m.%Y'), real_pub_issue) + event.planned_publication_date = real_pub_date + event.save() + messages.warning(request, _(u'Veröffentlichungsdatum wurde angepasst: %s') % real_pub_str) + event.workflow.update_status(status, request.user) if status.startswith('submit'): @@ -546,7 +614,21 @@ class EventUpdateView(EventPermissionMixin, generic.UpdateView): def form_valid(self, form): form.instance.editor = self.request.user - self.object = form.save() + event = form.save() + self.object = event + + if not event.workflow.has_reached_status('publishing*') and not event.workflow.has_reached_status('published*'): + cur_pub_date = event.planned_publication_date + real_pub_date, real_pub_issue = event.workflow.plan_publication(event.first_day, event.deadline) + if cur_pub_date != real_pub_date: + if real_pub_date is None: + real_pub_str = _(u'Unverzüglich') + else: + real_pub_str = u'%s (%s)' % (real_pub_date.strftime('%d.%m.%Y'), real_pub_issue) + event.planned_publication_date = real_pub_date + event.save() + messages.warning(self.request, _(u'Veröffentlichungsdatum wurde angepasst: %s') % real_pub_str) + return HttpResponseRedirect(self.get_success_url()) @method_decorator(login_required) diff --git a/dav_events/workflow.py b/dav_events/workflow.py index 1856480..499b09b 100644 --- a/dav_events/workflow.py +++ b/dav_events/workflow.py @@ -489,9 +489,8 @@ class BasicWorkflow(object): # # Misc logic # - # TODO: is a class method a good idea? - @classmethod - def plan_publication(cls, first_day, deadline=None): + @staticmethod + def plan_publication(first_day, deadline=None): app_config = apps.get_containing_app_config(__package__) if deadline: diff --git a/dav_registration/forms.py b/dav_registration/forms.py index bbeb9ee..2b10316 100644 --- a/dav_registration/forms.py +++ b/dav_registration/forms.py @@ -11,10 +11,14 @@ logger = logging.getLogger(__name__) class RegistrationForm(forms.ModelForm): not_dav_member = forms.BooleanField(required=False, - label=_('Ich bin noch kein DAV Mitglied.'), - help_text=_('Wenn du noch kein DAV Mitglied bist,' - ' oder deine Aufnahme noch in Arbeit ist,' - ' kreuze dieses Feld hier an.')) + label=_(u'Ich bin noch kein DAV Mitglied.'), + help_text=u'%s
\n%s' % ( + _(u'Wenn du noch kein DAV Mitglied bist,' + u' oder deine Aufnahme noch in Arbeit ist,' + u' kreuze dieses Feld hier an.'), + _(u'Spätestens zu Veranstaltungsbeginn muss' + u' jedoch eine Mitgliedschaft bestehen.') + )) class Meta: model = Registration @@ -26,7 +30,7 @@ class RegistrationForm(forms.ModelForm): 'note': forms.Textarea(attrs={'rows': 5}), } labels = { - 'apply_reduced_fee': _('Ich bin noch keine 25 Jahre alt oder besitze einen "Karlsruher Pass".'), + 'apply_reduced_fee': _(u'Ich bin noch keine 25 Jahre alt oder besitze einen "Karlsruher Pass".'), } def clean_year_of_birth(self): diff --git a/dav_registration/models.py b/dav_registration/models.py index fa82b73..d5cc281 100644 --- a/dav_registration/models.py +++ b/dav_registration/models.py @@ -164,6 +164,13 @@ Anmerkung: note=self.note, ) + def get_data_dict(self): + data = {} + for field in self._meta.fields: + if not field.primary_key: + data[field.name] = getattr(self, field.name) + return data + def clean(self): if self.dav_member and not self.dav_number: raise ValidationError({'dav_number': _('Wenn du DAV Mitglied bist, brauchen wir deine Mitgliedsnummer.')})