feat(members/excursion): automatically send crisis intervention list

pull/155/head
Christian Merten 7 months ago
parent d4137effa4
commit 1ada10fda4
Signed by: christian.merten
GPG Key ID: D953D69721B948B3

@ -247,3 +247,29 @@ Deine JDAV %(SEKTION)s""" % { 'SEKTION': SEKTION, 'RESPONSIBLE_MAIL': RESPONSIBL
ADDRESS = get_text('address', default="""JDAV %(SEKTION)s
%(STREET)s
%(PLACE)s""" % { 'SEKTION': SEKTION, 'STREET': SEKTION_STREET, 'PLACE': SEKTION_TOWN })
NOTIFY_EXCURSION_PARTICIPANT_LIST = get_text('notify_excursion_participant_list', default="""Hallo {name},
deine Ausfahrt {excursion} steht kurz bevor. Damit die Sektion dich im Notfall gut unterstützen kann, benötigt
die Geschäftsstelle eine aktuelle Kriseninterventionsliste, das heißt eine Teilnehmendenliste der Ausfahrt.
Das Verschicken der Liste passiert automatisch zum Zeitpunkt: {sending_time}.
Das sind die aktuell in der Ausfahrt eingetragenen Teilnehmenden:
{participants}
Falls diese Liste nicht mehr aktuell ist, gehe bitte umgehend auf {excursion_link} und trage die Daten nach.
Viele Grüße
Dein KOMPASS""")
SEND_EXCURSION_CRISIS_LIST = get_text('send_excursion_crisis_list', default="""Hallo zusammen,
vom {excursion_start} bis {excursion_end} findet die Ausfahrt {excursion} der Jugend statt. Die
Ausfahrt wird geleitet von {leaders}.
Im Anhang findet ihr die Kriseninterventionsliste.
Viele Grüße
Euer KOMPASS""")

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-27 23:00+0200\n"
"POT-Creation-Date: 2025-05-03 18:06+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -469,6 +469,15 @@ msgstr ""
msgid "Finance overview"
msgstr "Kostenübersicht"
#: members/admin.py
msgid "Inform youth leaders about sending of crisis intervention list."
msgstr ""
"Informiere Jugendleiter:innen über Versand der Kriseninterventionsliste."
#: members/admin.py
msgid "Send crisis intervention list."
msgstr "Kriseninterventionsliste verschicken"
#: members/apps.py
msgid "member administration"
msgstr "Teilnehmer*innenverwaltung"
@ -993,6 +1002,15 @@ msgstr "Ausfahrt"
msgid "Excursions"
msgstr "Ausfahrten"
#: members/models.py
msgid "Crisis intervention list for %(excursion)s from %(start)s to %(end)s"
msgstr "Kriseninterventionsliste für %(excursion)s vom %(start)s bis %(end)s"
#: members/models.py
#, python-format
msgid "Participant list for %(excursion)s from %(start)s to %(end)s"
msgstr "Teilnehmendenliste für %(excursion)s vom %(start)s bis %(end)s"
#: members/models.py
msgid "Title"
msgstr "Titel"
@ -1392,9 +1410,9 @@ msgid ""
"%(total_org_fee_theoretical)s € is charged against the other transactions."
msgstr ""
"Achtung: %(old_participant_count)s Teilnehmende der Ausfahrt sind 27 oder "
"älter. Für diese Teilnehmende(n) ist ein Org-Beitrag von %(org_fee)s € pro Tag "
"fällig. Durch die Länge der Ausfahrt von %(duration)s Tagen werden insgesamt "
"%(total_org_fee_theoretical)s € mit den Zuschüssen und "
"älter. Für diese Teilnehmende(n) ist ein Org-Beitrag von %(org_fee)s € pro "
"Tag fällig. Durch die Länge der Ausfahrt von %(duration)s Tagen werden "
"insgesamt %(total_org_fee_theoretical)s € mit den Zuschüssen und "
"Aufwandsentschädigungen verrechnet, sofern diese in Anspruch genommen werden."
#: members/templates/admin/freizeit_finance_overview.html

@ -0,0 +1,23 @@
# Generated by Django 4.2.20 on 2025-05-03 15:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0040_invitationtogroup_created_by'),
]
operations = [
migrations.AddField(
model_name='freizeit',
name='crisis_intervention_list_sent',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='freizeit',
name='notification_crisis_intervention_list_sent',
field=models.BooleanField(default=False),
),
]

@ -26,10 +26,12 @@ from django.conf import settings
from django.core.validators import MinValueValidator
from .rules import may_view, may_change, may_delete, is_own_training, is_oneself, is_leader, is_leader_of_excursion
from .pdf import render_tex
import rules
from contrib.models import CommonModel
from contrib.media import media_path
from contrib.rules import memberize_user, has_global_perm
from utils import cvt_to_decimal
from utils import cvt_to_decimal, coming_midnight
from dateutil.relativedelta import relativedelta
from schwifty import IBAN
@ -1217,6 +1219,10 @@ class Freizeit(CommonModel):
approval_comments = models.TextField(verbose_name=_('Approval comments'),
blank=True, default='')
# automatic sending of crisis intervention list
crisis_intervention_list_sent = models.BooleanField(default=False)
notification_crisis_intervention_list_sent = models.BooleanField(default=False)
def __str__(self):
"""String represenation"""
return self.name
@ -1609,6 +1615,61 @@ class Freizeit(CommonModel):
queryset = queryset.filter(Q(groups__in=groups) | Q(jugendleiter__pk=member.pk)).distinct()
return queryset
def send_crisis_intervention_list(self, sending_time=None):
"""
Send the crisis intervention list to the crisis invervention email, the
responsible and the youth leaders of this excursion.
"""
context = dict(memberlist=self, settings=settings)
start_date= timezone.localtime(self.date).strftime('%d.%m.%Y')
filename = render_tex(f"{self.code}_{self.name}_Krisenliste",
'members/crisis_intervention_list.tex', context,
date=self.date, save_only=True)
leaders = ", ".join([yl.name for yl in self.jugendleiter.all()])
start_date = timezone.localtime(self.date).strftime('%d.%m.%Y')
end_date = timezone.localtime(self.end).strftime('%d.%m.%Y')
# create email with attachment
send_mail(_('Crisis intervention list for %(excursion)s from %(start)s to %(end)s') %\
{ 'excursion': self.name,
'start': start_date,
'end': end_date },
settings.SEND_EXCURSION_CRISIS_LIST.format(excursion=self.name, leaders=leaders,
excursion_start=start_date,
excursion_end=end_date),
sender=settings.DEFAULT_SENDING_MAIL,
recipients=[settings.SEKTION_CRISIS_INTERVENTION_MAIL],
cc=[settings.RESPONSIBLE_MAIL] + [yl.email for yl in self.jugendleiter.all()],
attachments=[media_path(filename)])
self.crisis_intervention_list_sent = True
self.save()
def notify_leaders_crisis_intervention_list(self, sending_time=None):
"""
Send an email to the youth leaders of this excursion with a list of currently
registered participants and a heads-up that the crisis intervention list
will be automatically sent on the night of this day.
"""
participants = "\n".join([f"- {p.member.name}" for p in self.membersonlist.all()])
if not sending_time:
sending_time = coming_midnight().strftime("%d.%m.%y %H:%M")
elif not isinstance(sending_time, str):
sending_time = sending_time.strftime("%d.%m.%y %H:%M")
start_date = timezone.localtime(self.date).strftime('%d.%m.%Y')
end_date = timezone.localtime(self.end).strftime('%d.%m.%Y')
excursion_link = prepend_base_url(self.get_absolute_url())
for yl in self.jugendleiter.all():
yl.send_mail(_('Participant list for %(excursion)s from %(start)s to %(end)s') %\
{ 'excursion': self.name,
'start': start_date,
'end': end_date },
settings.NOTIFY_EXCURSION_PARTICIPANT_LIST.format(name=yl.prename,
excursion=self.name,
participants=participants,
sending_time=sending_time,
excursion_link=excursion_link))
self.notification_crisis_intervention_list_sent = True
self.save()
class MemberNoteList(models.Model):
"""

@ -1,7 +1,7 @@
from celery import shared_task
from django.utils import timezone
from django.conf import settings
from .models import MemberWaitingList
from .models import MemberWaitingList, Freizeit
@shared_task
def ask_for_waiting_confirmation():
@ -18,3 +18,31 @@ def ask_for_waiting_confirmation():
waiter.ask_for_wait_confirmation()
no += 1
return no
@shared_task
def send_crisis_intervention_list():
"""
Send crisis intervention lists for all excursions that start on the current day and
that have not been sent yet.
"""
no = 0
for excursion in Freizeit.objects.filter(date__date=timezone.now().date(),
crisis_intervention_list_sent=False):
excursion.send_crisis_intervention_list()
no += 1
return no
@shared_task
def send_notification_crisis_intervention_list():
"""
Send crisis intervention list notifiactions for all excursions that start on the next
day and that have not been sent yet.
"""
no = 0
for excursion in Freizeit.objects.filter(date__date=timezone.now().date() + timezone.timedelta(days=1),
notification_crisis_intervention_list_sent=False):
excursion.notify_leaders_crisis_intervention_list()
no += 1
return no

@ -631,11 +631,20 @@ class MemberAdminTestCase(AdminTestCase):
class FreizeitTestCase(BasicMemberTestCase):
def setUp(self):
super().setUp()
# this excursion is used for the counting tests
self.ex = Freizeit.objects.create(name='Wild trip', kilometers_traveled=120,
tour_type=GEMEINSCHAFTS_TOUR,
tour_approach=MUSKELKRAFT_ANREISE,
difficulty=1,
date=timezone.localtime())
# this excursion is used in the other tests
self.ex2 = Freizeit.objects.create(name='Wild trip 2', kilometers_traveled=120,
tour_type=GEMEINSCHAFTS_TOUR,
tour_approach=MUSKELKRAFT_ANREISE,
difficulty=1,
date=timezone.localtime())
self.ex2.jugendleiter.add(self.fritz)
self.ex2.save()
def _setup_test_sjr_application_numbers(self, n_yl, n_b27_local, n_b27_non_local):
for i in range(n_yl):
@ -749,6 +758,17 @@ class FreizeitTestCase(BasicMemberTestCase):
for i in range(10):
self._test_sjr_application_numbers(10, 10 - i, i)
def test_notify_leaders_crisis_intervention_list(self):
self.ex2.notification_crisis_intervention_list_sent = False
self.ex2.notify_leaders_crisis_intervention_list()
self.assertTrue(self.ex2.notification_crisis_intervention_list_sent)
self.ex2.notify_leaders_crisis_intervention_list(sending_time=timezone.now())
def test_send_crisis_intervention_list(self):
self.ex2.crisis_intervention_list_sent = False
self.ex2.send_crisis_intervention_list()
self.assertTrue(self.ex2.crisis_intervention_list_sent)
class PDFActionMixin:
def _test_pdf(self, name, pk, model='freizeit', invalid=False, username='superuser', post_data=None):

@ -1,5 +1,6 @@
from datetime import datetime
from django.db import models
from django.utils import timezone
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from decimal import Decimal, ROUND_HALF_DOWN
@ -80,3 +81,10 @@ def normalize_filename(filename, append_date=True, date=None):
filename = filename.replace(' ', '_').replace('&', '').replace('/', '_')
# drop umlauts, accents etc.
return unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore').decode()
def coming_midnight():
base = timezone.now() + timezone.timedelta(days=1)
return timezone.datetime(year=base.year, month=base.month, day=base.day,
hour=0, minute=0, second=0, microsecond=0,
tzinfo=base.tzinfo)

Loading…
Cancel
Save