diff --git a/tapir/shifts/forms.py b/tapir/shifts/forms.py
index 59d0c673e..9a52bf665 100644
--- a/tapir/shifts/forms.py
+++ b/tapir/shifts/forms.py
@@ -458,6 +458,28 @@ def clean(self):
return result
+class ShiftTemplateEndDateForm(forms.ModelForm):
+ cancellation_reason = forms.CharField(label=_("Cancellation Reason"), required=True)
+
+ class Meta:
+ model = ShiftTemplate
+ fields = ["end_date"]
+ widgets = {"end_date": DateInputTapir()}
+
+ def __init__(self, shift_template=None, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields["end_date"].required = True
+
+ def clean_end_date(self):
+ if (
+ self.cleaned_data["end_date"]
+ and self.instance.start_date is not None
+ and self.cleaned_data["end_date"] < self.instance.start_date
+ ):
+ raise ValidationError(_("The end date must be later than the start date."))
+ return self.cleaned_data["end_date"]
+
+
class CreateShiftAccountEntryForm(forms.ModelForm):
class Meta:
model = ShiftAccountEntry
diff --git a/tapir/shifts/migrations/0076_shifttemplate_end_date.py b/tapir/shifts/migrations/0076_shifttemplate_end_date.py
new file mode 100644
index 000000000..c4d80047b
--- /dev/null
+++ b/tapir/shifts/migrations/0076_shifttemplate_end_date.py
@@ -0,0 +1,22 @@
+# Generated by Django 5.2.15 on 2026-06-14 17:37
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("shifts", "0075_shiftwatch_watched_capabilities"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="shifttemplate",
+ name="end_date",
+ field=models.DateField(
+ blank=True,
+ help_text="All shifts after this date will be cancelled.",
+ null=True,
+ ),
+ ),
+ ]
diff --git a/tapir/shifts/models.py b/tapir/shifts/models.py
index 14e78de3f..04fdddf2f 100644
--- a/tapir/shifts/models.py
+++ b/tapir/shifts/models.py
@@ -177,6 +177,11 @@ class ShiftTemplate(models.Model):
"This determines from which date shifts should be generated from this ABCD shift."
),
)
+ end_date = models.DateField(
+ blank=True,
+ null=True,
+ help_text=_("All shifts after this date will be cancelled."),
+ )
flexible_time = models.BooleanField(
verbose_name=_("Flexible time"),
help_text=_(
@@ -291,10 +296,11 @@ def update_future_generated_shifts_to_fit_this(self):
shift.update_to_fit_template()
def clean(self):
- if self.start_time >= self.end_time:
- raise ValidationError(
- f"The shift must end after it starts. Given start time: {self.start_time}. Given end time: {self.end_time}"
- )
+ if self.start_time and self.end_time:
+ if self.start_time >= self.end_time:
+ raise ValidationError(
+ f"The shift must end after it starts. Given start time: {self.start_time}. Given end time: {self.end_time}"
+ )
class RequiredCapabilitiesMixin:
diff --git a/tapir/shifts/services/shift_generator.py b/tapir/shifts/services/shift_generator.py
index d780a78fd..f5b48b93a 100644
--- a/tapir/shifts/services/shift_generator.py
+++ b/tapir/shifts/services/shift_generator.py
@@ -57,8 +57,13 @@ def create_shifts_for_group(
start_date_in_the_past_or_null = Q(start_date__lte=at_date) | Q(
start_date__isnull=True
)
- shift_templates = ShiftTemplate.objects.filter(group=group).filter(
- start_date_in_the_past_or_null
+ end_date_in_the_future_or_null = Q(end_date__gte=at_date) | Q(
+ end_date__isnull=True
+ )
+ shift_templates = (
+ ShiftTemplate.objects.filter(group=group)
+ .filter(start_date_in_the_past_or_null)
+ .filter(end_date_in_the_future_or_null)
)
if filter_shift_template_ids is not None:
diff --git a/tapir/shifts/templates/shifts/shift_detail.html b/tapir/shifts/templates/shifts/shift_detail.html
index bb674f208..2c0fd8ca8 100644
--- a/tapir/shifts/templates/shifts/shift_detail.html
+++ b/tapir/shifts/templates/shifts/shift_detail.html
@@ -234,12 +234,17 @@
#{{ forloop.counter }}
{% elif not slot.is_occupied %}
{% blocktranslate asvar self_register_tooltip %}You can only register
yourself
- for a shift if:
- -You are not registered to another slot in that shift
+ for a shift if:
+
+ -You are not registered to another slot in that shift
+
-You have the required qualification (if you want to get a
- qualification, contact the member office)
- -The shift is in the future
- -The shift is not cancelled (holidays...)
+ qualification, contact the member office)
+
+ -The shift is in the future
+
+ -The shift is not cancelled (holidays...)
+
{% endblocktranslate %}
{% autoescape off %}
diff --git a/tapir/shifts/templates/shifts/shift_template_detail.html b/tapir/shifts/templates/shifts/shift_template_detail.html
index b1ee5d22d..132770826 100644
--- a/tapir/shifts/templates/shifts/shift_template_detail.html
+++ b/tapir/shifts/templates/shifts/shift_template_detail.html
@@ -28,6 +28,11 @@
diff --git a/tapir/shifts/templates/shifts/shift_template_set_end_date.html b/tapir/shifts/templates/shifts/shift_template_set_end_date.html
new file mode 100644
index 000000000..2d0e14072
--- /dev/null
+++ b/tapir/shifts/templates/shifts/shift_template_set_end_date.html
@@ -0,0 +1,29 @@
+{% extends "shifts/base.html" %}
+{% load django_bootstrap5 %}
+{% load static %}
+{% load i18n %}
+{% load core %}
+{% block title %}
+ {% translate 'Set end date for' %}: {{ shift_template.get_display_name }}
+{% endblock title %}
+{% block content %}
+
+{% endblock content %}
diff --git a/tapir/shifts/tests/services/shift_generator/test_create_shifts_for_group.py b/tapir/shifts/tests/services/shift_generator/test_create_shifts_for_group.py
index 09bb2577e..62d16df2c 100644
--- a/tapir/shifts/tests/services/shift_generator/test_create_shifts_for_group.py
+++ b/tapir/shifts/tests/services/shift_generator/test_create_shifts_for_group.py
@@ -113,3 +113,22 @@ def test_createShiftsForGroup_withoutHolidayCancellation_doesntCancelsHolidays(
)
self.assertFalse(shift.cancelled)
self.assertIsNone(shift.cancelled_reason)
+
+ def test_createShiftForGroup_endDate_noShiftsCreatedPastEndDate(self):
+ group_a = ShiftTemplateGroup.objects.create(name="A")
+
+ template_with_future_end_date = ShiftTemplateFactory.create(
+ group=group_a, end_date=datetime.date(2025, 11, 20)
+ )
+
+ # should be created
+ ShiftGenerator.create_shifts_for_group(
+ at_date=datetime.date(2025, 11, 10), group=group_a
+ )
+
+ # should not be created
+ ShiftGenerator.create_shifts_for_group(
+ at_date=datetime.date(2025, 11, 24), group=group_a
+ )
+
+ self.assertEqual(1, template_with_future_end_date.generated_shifts.count())
diff --git a/tapir/shifts/tests/test_shifttemplateenddateview.py b/tapir/shifts/tests/test_shifttemplateenddateview.py
new file mode 100644
index 000000000..cc45960fe
--- /dev/null
+++ b/tapir/shifts/tests/test_shifttemplateenddateview.py
@@ -0,0 +1,120 @@
+import datetime
+
+from django.contrib.messages import get_messages
+from django.urls import reverse
+from django.utils import timezone
+from django_extensions.jobs import weekly
+
+from tapir.accounts.tests.factories.factories import TapirUserFactory
+from tapir.shifts.tests.factories import ShiftTemplateFactory
+from tapir.shifts.tests.utils import register_user_to_shift_template
+from tapir.utils.tests_utils import TapirFactoryTestBase
+
+
+class TestShiftTemplateEndView(TapirFactoryTestBase):
+
+ def setUp(self):
+ super().setUp()
+ self.shift_template = ShiftTemplateFactory.create(
+ start_date=timezone.now().date(), weekday=0
+ )
+ self.url = reverse(
+ "shifts:shift_template_set_end_date", kwargs={"pk": self.shift_template.pk}
+ )
+
+ def test_shiftTemplate_setEndDateInBetween_futureShiftsAfterEndDateAreCancelled(
+ self,
+ ):
+ self.login_as_employee()
+
+ user = TapirUserFactory.create(is_in_member_office=False)
+ register_user_to_shift_template(self.client, user, self.shift_template)
+
+ today = timezone.now().date()
+ shift_1 = self.shift_template.create_shift_if_necessary(
+ today + datetime.timedelta(days=7)
+ )
+ shift_2 = self.shift_template.create_shift_if_necessary(
+ today + datetime.timedelta(days=14)
+ )
+ shift_3 = self.shift_template.create_shift_if_necessary(
+ today + datetime.timedelta(days=21)
+ )
+
+ end_date = today + datetime.timedelta(days=14)
+ cancellation_reason = "Shift Template ended"
+
+ response = self.client.post(
+ self.url,
+ {
+ "end_date": end_date.strftime("%Y-%m-%d"),
+ "cancellation_reason": cancellation_reason,
+ },
+ )
+
+ self.assertRedirects(
+ response,
+ reverse(
+ "shifts:shift_template_detail", kwargs={"pk": self.shift_template.pk}
+ ),
+ )
+
+ self.shift_template.refresh_from_db()
+ shift_1.refresh_from_db()
+ shift_2.refresh_from_db()
+ shift_3.refresh_from_db()
+
+ self.assertEqual(self.shift_template.end_date, end_date)
+
+ self.assertFalse(shift_1.cancelled)
+ self.assertFalse(shift_2.cancelled)
+
+ self.assertTrue(shift_3.cancelled)
+ self.assertEqual(shift_3.cancelled_reason, cancellation_reason)
+
+ def test_shiftTemplate_sendEndDateAfterAllShifts_noShiftIsCancelled(self):
+ self.login_as_employee()
+ today = timezone.now().date()
+ end_date = today + datetime.timedelta(days=365)
+ cancellation_reason = "Shift Template ended"
+ shift = self.shift_template.create_shift_if_necessary(
+ today + datetime.timedelta(days=7)
+ )
+
+ response = self.client.post(
+ self.url,
+ {
+ "end_date": end_date.strftime("%Y-%m-%d"),
+ "cancellation_reason": cancellation_reason,
+ },
+ )
+
+ self.shift_template.refresh_from_db()
+ shift.refresh_from_db()
+ self.assertEqual(self.shift_template.end_date, end_date)
+
+ self.assertFalse(shift.cancelled)
+
+ msgs = list(get_messages(response.wsgi_request))
+ self.assertGreater(len(msgs), 0)
+
+ def test_shiftTemplate_sendEndDate_endDateBeforeStartDate_showsValidationError(
+ self,
+ ):
+ self.login_as_employee()
+
+ end_date = self.shift_template.start_date - datetime.timedelta(days=7)
+
+ response = self.client.post(
+ self.url,
+ {
+ "end_date": end_date.strftime("%Y-%m-%d"),
+ "cancellation_reason": "Test",
+ },
+ )
+
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("end_date", response.context["form"].errors)
+
+ self.shift_template.refresh_from_db()
+ self.assertIsNone(self.shift_template.end_date)
diff --git a/tapir/shifts/urls.py b/tapir/shifts/urls.py
index 7ec077acd..9f6281e50 100644
--- a/tapir/shifts/urls.py
+++ b/tapir/shifts/urls.py
@@ -1,6 +1,7 @@
from django.urls import path
from tapir.shifts import views
+from tapir.shifts.views import ShiftTemplateEndDateView
app_name = "shifts"
urlpatterns = [
@@ -240,4 +241,9 @@
views.RecurringShiftwatchListView.as_view(),
name="shiftwatch_overview",
),
+ path(
+ "shift_template//set_end_date/",
+ ShiftTemplateEndDateView.as_view(),
+ name="shift_template_set_end_date",
+ ),
]
diff --git a/tapir/shifts/views/views.py b/tapir/shifts/views/views.py
index acfc2ce91..d40c1788a 100644
--- a/tapir/shifts/views/views.py
+++ b/tapir/shifts/views/views.py
@@ -39,6 +39,7 @@
from tapir.shifts.forms import (
CreateShiftAccountEntryForm,
RecurringShiftWatchForm,
+ ShiftTemplateEndDateForm,
ShiftUserDataForm,
ShiftWatchForm,
)
@@ -54,6 +55,7 @@
ShiftWatch,
UpdateShiftUserDataLogEntry,
)
+from tapir.shifts.services.shift_cancellation_service import ShiftCancellationService
from tapir.shifts.services.shift_watch_creation_service import ShiftWatchCreator
from tapir.shifts.templatetags.shifts import shift_name_as_class
from tapir.utils.user_utils import UserUtils
@@ -329,6 +331,47 @@ def get_queryset(self):
)
+class ShiftTemplateEndDateView(
+ LoginRequiredMixin,
+ PermissionRequiredMixin,
+ generic.UpdateView,
+):
+ permission_required = PERMISSION_SHIFTS_MANAGE
+ form_class = ShiftTemplateEndDateForm
+ template_name = "shifts/shift_template_set_end_date.html"
+ model = ShiftTemplate
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context["shift_template"] = self.object
+ return context
+
+ def get_success_url(self):
+ return reverse("shifts:shift_template_detail", kwargs={"pk": self.object.pk})
+
+ def form_valid(self, form):
+ with transaction.atomic():
+ response = super().form_valid(form)
+ end_date = form.cleaned_data["end_date"]
+ cancellation_reason = form.cleaned_data["cancellation_reason"]
+ shifts_to_cancel = Shift.objects.filter(
+ shift_template=self.object,
+ start_time__date__gt=end_date,
+ deleted=False,
+ cancelled=False,
+ )
+ for shift in shifts_to_cancel:
+ shift.cancelled_reason = cancellation_reason
+ ShiftCancellationService.cancel(shift)
+
+ messages.success(
+ self.request,
+ _("Shift template was successfully cancelled"),
+ )
+
+ return response
+
+
class ShiftUserDataTable(django_tables2.Table):
class Meta:
model = ShiftUserData
diff --git a/tapir/translations/locale/de/LC_MESSAGES/django.po b/tapir/translations/locale/de/LC_MESSAGES/django.po
index 0e17400fd..700b2cb7a 100644
--- a/tapir/translations/locale/de/LC_MESSAGES/django.po
+++ b/tapir/translations/locale/de/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-07-06 20:45+0200\n"
+"POT-Creation-Date: 2026-07-10 18:22+0200\n"
"PO-Revision-Date: 2025-07-07 13:06+0000\n"
"Last-Translator: Weblate Admin \n"
"Language-Team: German \n"
@@ -343,7 +343,7 @@ msgstr "Benutzername bearbeiten"
#: core/templates/core/featureflag_list.html:30
#: shifts/templates/shifts/shift_detail.html:65
#: shifts/templates/shifts/shift_template_detail.html:29
-#: shifts/templates/shifts/shift_template_detail.html:117
+#: shifts/templates/shifts/shift_template_detail.html:122
#: shifts/templates/shifts/user_shifts_overview_tag.html:14
msgid "Edit"
msgstr "Bearbeiten"
@@ -652,7 +652,7 @@ msgstr "Wird normalerweise leer gelassen. Das Datum kann in der Zukunft liegen,
msgid "Number of shares to create"
msgstr "Anzahl zu erstellender Anteile"
-#: coop/forms.py:67
+#: coop/forms.py:67 shifts/forms.py:479
msgid "The end date must be later than the start date."
msgstr "Das Enddatum muss später als das Start-Datum sein"
@@ -1112,8 +1112,8 @@ msgstr "Bewerber*in erzeugen"
#: coop/templates/coop/draftuser_register_form.html:29
#: shifts/templates/shifts/register_user_to_shift_slot.html:27
#: shifts/templates/shifts/register_user_to_shift_slot_template.html:26
-#: shifts/templates/shifts/shift_detail.html:249
-#: shifts/templates/shifts/shift_template_detail.html:88
+#: shifts/templates/shifts/shift_detail.html:254
+#: shifts/templates/shifts/shift_template_detail.html:93
msgid "Register"
msgstr "Anmelden"
@@ -2275,7 +2275,7 @@ msgstr ""
" "
#: coop/templates/coop/membershipresignation_detail.html:61
-#: shifts/models.py:535
+#: shifts/models.py:541
msgid "Cancellation reason"
msgstr "Grund der Kündigung"
@@ -2522,15 +2522,15 @@ msgstr ""
" "
#: coop/templates/coop/tags/user_coop_share_ownership_list_tag.html:56
-#: shifts/models.py:933 shifts/templates/shifts/shift_day_printable.html:214
+#: shifts/models.py:939 shifts/templates/shifts/shift_day_printable.html:214
#: shifts/templates/shifts/shift_day_printable.html:276
-#: shifts/templates/shifts/shift_detail.html:304
+#: shifts/templates/shifts/shift_detail.html:309
#: shifts/templates/shifts/shift_detail_printable.html:51
msgid "Attended"
msgstr "Teilgenommen"
#: coop/templates/coop/tags/user_coop_share_ownership_list_tag.html:58
-#: shifts/models.py:932
+#: shifts/models.py:938
msgid "Pending"
msgstr "Ausstehend"
@@ -2703,7 +2703,7 @@ msgstr "Hat Qualifikation"
msgid "Does not have qualification"
msgstr "Hat die Qualifikation nicht"
-#: coop/views/shareowner.py:656 shifts/forms.py:788
+#: coop/views/shareowner.py:656 shifts/forms.py:810
msgid "ABCD Week"
msgstr "ABCD-Woche"
@@ -2836,8 +2836,8 @@ msgstr ""
msgid "List of all emails"
msgstr "Liste alle E-Mails"
-#: core/templates/core/email_list.html:39 shifts/models.py:502
-#: shifts/models.py:1071 shifts/templates/shifts/user_shift_account_log.html:29
+#: core/templates/core/email_list.html:39 shifts/models.py:508
+#: shifts/models.py:1077 shifts/templates/shifts/user_shift_account_log.html:29
msgid "Description"
msgstr "Beschreibung"
@@ -3190,86 +3190,86 @@ msgstr "Das ausgewählte Mitglied muss ein investierendes Mitglied sein."
msgid "This member is registered to at least one ABCD shift. Please confirm the change of attendance mode with the checkbox below."
msgstr ""
-#: shifts/forms.py:496
+#: shifts/forms.py:462 shifts/forms.py:629
+msgid "Cancellation Reason"
+msgstr "Grund der Absage"
+
+#: shifts/forms.py:518
msgid "I have read the warning about the cancelled attendances and confirm that the exemption should be created"
msgstr "Ich habe die Warnung über die abgesagte Schicht-Anwesenheit gelesen und bestätige, dass die Befreiung erzeugt werden soll"
-#: shifts/forms.py:503
+#: shifts/forms.py:525
msgid "I have read the warning about the cancelled ABCD attendances and confirm that the exemption should be created"
msgstr "Ich habe die Warnung über die abgebrochene ABCD-Teilnahme gelesen und bestätige, dass die Befreiung erzeugt werden soll"
-#: shifts/forms.py:561
+#: shifts/forms.py:583
#, python-format
msgid "The user will be unregistered from the following ABCD shifts because the exemption is longer than %(number_of_cycles)s cycles: %(attendances_display)s "
msgstr "Das Mitglied wird von den folgenden ABCD-Schichten abgemeldet, weil die Befreiung länger als %(number_of_cycles)s Schichtzyklen ist: %(attendances_display)s "
-#: shifts/forms.py:582
+#: shifts/forms.py:604
msgid "This shift has been deleted. It is not possible to cancel it."
msgstr "Diese Schicht wurde bereits gelöscht. Es ist nicht möglich diese abzusagen."
-#: shifts/forms.py:593 shifts/templates/shifts/shift_detail.html:8
+#: shifts/forms.py:615 shifts/templates/shifts/shift_detail.html:8
#: shifts/templates/shifts/shift_detail.html:30
msgid "Shift"
msgstr "Schicht"
-#: shifts/forms.py:594
+#: shifts/forms.py:616
msgid "already cancelled"
msgstr "bereits bgesagt"
-#: shifts/forms.py:607
-msgid "Cancellation Reason"
-msgstr "Grund der Absage"
-
-#: shifts/forms.py:608
+#: shifts/forms.py:630
msgid "This reason will be applied to all cancelled shifts."
msgstr ""
-#: shifts/forms.py:636
+#: shifts/forms.py:658
msgid "I understand that updating this ABCD shift will update all the corresponding future shifts"
msgstr ""
-#: shifts/forms.py:655
+#: shifts/forms.py:677
msgid "ABCD-Week"
msgstr "ABCD-Woche"
-#: shifts/forms.py:659
+#: shifts/forms.py:681
msgid "Weekdays"
msgstr "Wochentage"
-#: shifts/forms.py:684
+#: shifts/forms.py:706
msgid "I understand that adding or editing a slot to this ABCD shift will affect all the corresponding future shifts"
msgstr ""
-#: shifts/forms.py:693
+#: shifts/forms.py:715
msgid "I understand that this will delete the shift exemption and create a membership pause"
msgstr ""
-#: shifts/forms.py:725 shifts/forms.py:793 shifts/views/views.py:419
-#: shifts/views/views.py:420
+#: shifts/forms.py:747 shifts/forms.py:815 shifts/views/views.py:462
+#: shifts/views/views.py:463
msgid "Shift changes you would like to be informed about"
msgstr "Schicht-Änderungen, bei denen du informiert werden möchtest"
-#: shifts/forms.py:732
+#: shifts/forms.py:754
msgid "Notify me when these capabilities become available or unavailable"
msgstr ""
-#: shifts/forms.py:736
+#: shifts/forms.py:758
msgid "Get notified when someone with specific skills registers or unregisters"
msgstr ""
-#: shifts/forms.py:782 shifts/templates/shifts/shift_template_detail.html:7
+#: shifts/forms.py:804 shifts/templates/shifts/shift_template_detail.html:7
#: shifts/templates/shifts/shift_template_detail.html:15
#: shifts/templates/shifts/user_shifts_overview_tag.html:31
#: shifts/templates/shifts/user_shifts_overview_tag.html:43
msgid "ABCD Shift"
msgstr "ABCD-Schicht"
-#: shifts/forms.py:799
+#: shifts/forms.py:821
#, python-format
msgid "If weekdays or %(shift_template_group)s are selected, %(shift_templates)s may not be selected, and vice versa."
msgstr ""
-#: shifts/forms.py:803
+#: shifts/forms.py:825
#, python-format
msgid "At least one of the fields (%(shift_templates)s, weekdays, or %(shift_template_group)s) must be selected."
msgstr ""
@@ -3334,7 +3334,7 @@ msgstr "Mir ist klar, dass ich bei dieser Schicht möglicherweise schwere Gewich
msgid "I understand that I may need to work high, for example up a ladder. I do not suffer from fear of heights."
msgstr "Mir ist klar, dass ich möglicherweise in großer Höhe arbeiten muss, zum Beispiel auf einer Leiter. Ich leide nicht unter Höhenangst."
-#: shifts/models.py:166 shifts/models.py:496
+#: shifts/models.py:166 shifts/models.py:502
msgid "If there are less members registered to a shift than that number, it will be highlighted in the shift calendar. The number of required attendances can't be higher than the slots in the resp. shift."
msgstr "Wenn weniger Mitlieder als diese Nummer zur Schicht registriert sind, wird diese blau angezeigt. Die Anzahl der benltigten Mitglieder kann nicht höher sein als die Anzahl vorhandener Slots."
@@ -3342,106 +3342,110 @@ msgstr "Wenn weniger Mitlieder als diese Nummer zur Schicht registriert sind, wi
msgid "This determines from which date shifts should be generated from this ABCD shift."
msgstr ""
-#: shifts/models.py:181 shifts/models.py:510
+#: shifts/models.py:183
+msgid "All shifts after this date will be cancelled."
+msgstr ""
+
+#: shifts/models.py:186 shifts/models.py:516
#: shifts/templates/shifts/shift_block_tag.html:10
msgid "Flexible time"
msgstr "Zeit flexibel"
-#: shifts/models.py:183 shifts/models.py:512
+#: shifts/models.py:188 shifts/models.py:518
msgid "If enabled, members who register for that shift can choose themselves the time where they come do their shift."
msgstr ""
-#: shifts/models.py:408 shifts/models.py:824
+#: shifts/models.py:414 shifts/models.py:830
#: shifts/templates/shifts/shift_detail.html:118
-#: shifts/templates/shifts/shift_template_detail.html:46
+#: shifts/templates/shifts/shift_template_detail.html:51
msgid "Chosen time"
msgstr "Ausgewählte Uhrzeit"
-#: shifts/models.py:410
+#: shifts/models.py:416
msgid "This shift lets you choose at what time you come during the day of the shift. In order to help organising the attendance, please specify when you expect to come.Setting or updating this field will set the time for all individual shifts generated from this ABCD shift.You can update the time of a single shift individually and at any time on the shift page."
msgstr ""
-#: shifts/models.py:494
+#: shifts/models.py:500
msgid "Number of required attendances"
msgstr "Anzahl notwendiger Teilnehmender"
-#: shifts/models.py:503
+#: shifts/models.py:509
msgid "Is shown on the shift page below the title"
msgstr ""
-#: shifts/models.py:523 shifts/models.py:529
+#: shifts/models.py:529 shifts/models.py:535
msgid "If 'flexible time' is enabled, then the time component is ignored"
msgstr ""
-#: shifts/models.py:826
+#: shifts/models.py:832
msgid "This shift lets you choose at what time you come during the day of the shift. In order to help organising the attendance, please specify when you expect to come."
msgstr "Diese Schicht ermöglicht dir auszusuchen, wann du kommen magst. Um die Planung zu erleichtern, gib bitte deine erwartete Ankunftszeit an."
-#: shifts/models.py:934 shifts/templates/shifts/shift_detail.html:314
+#: shifts/models.py:940 shifts/templates/shifts/shift_detail.html:319
#: shifts/templates/shifts/shift_detail_printable.html:52
msgid "Missed"
msgstr "Nicht erschienen"
-#: shifts/models.py:935 shifts/templates/shifts/shift_day_printable.html:216
+#: shifts/models.py:941 shifts/templates/shifts/shift_day_printable.html:216
#: shifts/templates/shifts/shift_day_printable.html:281
#: shifts/templates/shifts/shift_day_printable.html:283
-#: shifts/templates/shifts/shift_detail.html:344
+#: shifts/templates/shifts/shift_detail.html:349
#: shifts/templates/shifts/shift_detail_printable.html:53
msgid "Excused"
msgstr "Entschuldigt"
-#: shifts/models.py:936 shifts/templates/shifts/shift_detail.html:352
+#: shifts/models.py:942 shifts/templates/shifts/shift_detail.html:357
msgid "Cancelled"
msgstr "Abgesagt"
-#: shifts/models.py:937 shifts/templates/shifts/shift_day_printable.html:264
-#: shifts/templates/shifts/shift_detail.html:336
+#: shifts/models.py:943 shifts/templates/shifts/shift_day_printable.html:264
+#: shifts/templates/shifts/shift_detail.html:341
#: shifts/templates/shifts/shift_detail_printable.html:94
#: shifts/templates/shifts/shift_filters.html:83
msgid "Looking for a stand-in"
msgstr "Sucht Vertretung"
-#: shifts/models.py:970
+#: shifts/models.py:976
msgid "🏠 ABCD"
msgstr "🏠 ABCD"
-#: shifts/models.py:971
+#: shifts/models.py:977
msgid "✈ Flying"
msgstr "✈ Fliegend"
-#: shifts/models.py:972
+#: shifts/models.py:978
msgid "❄ Frozen"
msgstr "❄ Eingefroren"
-#: shifts/models.py:1003
+#: shifts/models.py:1009
msgid "Is frozen"
msgstr "Ist eingefroren"
-#: shifts/models.py:1177
+#: shifts/models.py:1183
msgid "Cycle start date"
msgstr "Anfangsdatum"
-#: shifts/models.py:1198
+#: shifts/models.py:1204
msgid "Shift is almost full, only one spot left."
msgstr "Die Schicht ist fast voll, es ist nur noch ein Platz übrig."
-#: shifts/models.py:1199
+#: shifts/models.py:1205
msgid "Shift is full now."
msgstr "Die Schicht ist jetzt voll."
-#: shifts/models.py:1200
+#: shifts/models.py:1206
msgid "The Shift is understaffed!"
msgstr "Die Schicht ist jetzt unterbesetzt."
-#: shifts/models.py:1201
+#: shifts/models.py:1207
msgid "Shift stable: not understaffed, not fully staffed."
msgstr "Entwarnung. Die schicht ist nicht länger unterbesetzt, aber es noch Platz übrig"
-#: shifts/models.py:1203
+#: shifts/models.py:1209
msgid "One new attendance or more registered, but the shift is neither understaffed nor full or almost full."
msgstr "Ein Mitglied oder mehr hat sich registriert, aber die Schicht ist weder unterbesetzt noch (fast) voll"
-#: shifts/models.py:1206
+#: shifts/models.py:1212
msgid "One attendance or more un-registered, but the shift is neither understaffed nor full or almost full."
msgstr "Ein Mitglied oder mehr hat sich abgemeldet, aber die Schicht ist weder unterbesetzt noch (fast) voll"
@@ -4306,7 +4310,7 @@ msgstr "Flexible Arbeitszeit nicht angegeben"
#: shifts/templates/shifts/shift_day_printable.html:259
#: shifts/templates/shifts/shift_detail.html:145
#: shifts/templates/shifts/shift_detail_printable.html:90
-#: shifts/templates/shifts/shift_template_detail.html:68
+#: shifts/templates/shifts/shift_template_detail.html:73
msgid "Shift partner: "
msgstr "Schicht-Partner: "
@@ -4370,12 +4374,12 @@ msgid "Number"
msgstr "Nummer"
#: shifts/templates/shifts/shift_detail.html:112
-#: shifts/templates/shifts/shift_template_detail.html:42
+#: shifts/templates/shifts/shift_template_detail.html:47
msgid "Details"
msgstr "Details"
#: shifts/templates/shifts/shift_detail.html:113
-#: shifts/templates/shifts/shift_template_detail.html:44
+#: shifts/templates/shifts/shift_template_detail.html:49
msgid "Registered user"
msgstr "Angemeldete*r Nutzer*in"
@@ -4388,7 +4392,7 @@ msgid "Do you meet the requirements?"
msgstr "Erfüllst du die Voraussetzungen?"
#: shifts/templates/shifts/shift_detail.html:121
-#: shifts/templates/shifts/shift_template_detail.html:49
+#: shifts/templates/shifts/shift_template_detail.html:54
msgid "Member-Office actions"
msgstr "Mitgliederbüro Aktionen"
@@ -4409,7 +4413,7 @@ msgid "Cancels the search for a stand-in. Use this if you want to attend the shi
msgstr "Beendet die Suche nach Vertretung. Benutze dies, wenn du die Schicht wahrnehmen möchtest."
#: shifts/templates/shifts/shift_detail.html:185
-#: shifts/templates/shifts/shift_detail.html:325
+#: shifts/templates/shifts/shift_detail.html:330
msgid "Cancel looking for a stand-in"
msgstr "Beende die Suche nach Vertretung"
@@ -4479,12 +4483,17 @@ msgstr "Melde mich ab"
msgid ""
"You can only register\n"
" yourself\n"
-" for a shift if:
\n"
-" -You are not registered to another slot in that shift
\n"
+" for a shift if:\n"
+"
\n"
+" -You are not registered to another slot in that shift\n"
+"
\n"
" -You have the required qualification (if you want to get a\n"
-" qualification, contact the member office)
\n"
-" -The shift is in the future
\n"
-" -The shift is not cancelled (holidays...)
\n"
+" qualification, contact the member office)\n"
+"
\n"
+" -The shift is in the future\n"
+"
\n"
+" -The shift is not cancelled (holidays...)\n"
+"
\n"
" "
msgstr ""
"Du kannst dich nur für eine Schicht anmelden, wenn\n"
@@ -4493,12 +4502,12 @@ msgstr ""
"- die Schicht in der Zukunft liegt\n"
" "
-#: shifts/templates/shifts/shift_detail.html:277
-#: shifts/templates/shifts/shift_template_detail.html:99
+#: shifts/templates/shifts/shift_detail.html:282
+#: shifts/templates/shifts/shift_template_detail.html:104
msgid "Not specified"
msgstr ""
-#: shifts/templates/shifts/shift_detail.html:360
+#: shifts/templates/shifts/shift_detail.html:365
msgid "Edit slot"
msgstr "Slot bearbeiten"
@@ -4623,23 +4632,29 @@ msgstr ""
msgid "Duplicate"
msgstr "Dopplung"
-#: shifts/templates/shifts/shift_template_detail.html:38
+#: shifts/templates/shifts/shift_template_detail.html:34
+#, fuzzy
+#| msgid "Pay out end date"
+msgid "Set end date"
+msgstr "Auszahlungsende"
+
+#: shifts/templates/shifts/shift_template_detail.html:43
msgid "List of slots for this ABCD shifts"
msgstr "Liste des Slots für diese ABCD-Schicht"
-#: shifts/templates/shifts/shift_template_detail.html:43
+#: shifts/templates/shifts/shift_template_detail.html:48
msgid "Requirements"
msgstr ""
-#: shifts/templates/shifts/shift_template_detail.html:77
+#: shifts/templates/shifts/shift_template_detail.html:82
msgid "Unregister"
msgstr "Abmelden"
-#: shifts/templates/shifts/shift_template_detail.html:126
+#: shifts/templates/shifts/shift_template_detail.html:131
msgid "Future generated Shifts"
msgstr "Zukünftige erstellte Schichten"
-#: shifts/templates/shifts/shift_template_detail.html:136
+#: shifts/templates/shifts/shift_template_detail.html:141
msgid "Past generated Shifts"
msgstr "Vergangene Schichten"
@@ -4678,6 +4693,27 @@ msgstr ""
msgid "Calendar of ABCD shifts"
msgstr "Kalender der ABCD-Schichten"
+#: shifts/templates/shifts/shift_template_set_end_date.html:7
+#: shifts/templates/shifts/shift_template_set_end_date.html:11
+#, fuzzy
+#| msgid "Pay out end date"
+msgid "Set end date for"
+msgstr "Auszahlungsende"
+
+#: shifts/templates/shifts/shift_template_set_end_date.html:17
+msgid "Warning"
+msgstr ""
+
+#: shifts/templates/shifts/shift_template_set_end_date.html:18
+msgid "All shifts generated from this ABCD-shift after the selected date will be cancelled. Registered members will be notified."
+msgstr ""
+
+#: shifts/templates/shifts/shift_template_set_end_date.html:23
+#, fuzzy
+#| msgid "Set shift attendance to"
+msgid "Set end date and cancel shifts"
+msgstr "Ändere den Schichtstatus zu"
+
#: shifts/templates/shifts/shiftexemption_list.html:38
msgid "Create shift exemption"
msgstr "Schicht-Befreiung erzeugen"
@@ -4709,7 +4745,7 @@ msgid "Recurring Shift Watches"
msgstr "Wiederholende Schichtbeobachtungen"
#: shifts/templates/shifts/shiftwatch_overview.html:25
-#: shifts/views/views.py:464
+#: shifts/views/views.py:507
msgid "Create a rule for recurring Shift Watches"
msgstr "Erstelle eine Regel für sich wiederholende Schichtbeobachtungen"
@@ -5043,7 +5079,7 @@ msgid "No watched shifts"
msgstr "Keine beobachteten Schichten"
#: shifts/templatetags/shifts.py:69 shifts/templatetags/shifts.py:174
-#: shifts/views/views.py:150
+#: shifts/views/views.py:152
msgid "General"
msgstr "Allgemein"
@@ -5142,27 +5178,33 @@ msgstr "Es konnte keine vollendete Schicht gefunden werden, die als Solidarität
msgid "Solidarity Shift given. Account Balance debited with -1."
msgstr "Die Solidaritätsschicht wurde vergeben. Von deinem Kontostand wird nun eine Schicht abgezogen."
-#: shifts/views/views.py:130 shifts/views/views.py:135
+#: shifts/views/views.py:132 shifts/views/views.py:137
#, python-format
msgid "Edit user shift data: %(name)s"
msgstr "Bearbeite Nutzer*in-Schichtdaten: %(name)s"
-#: shifts/views/views.py:203
+#: shifts/views/views.py:205
#, python-format
msgid "Shift account: %(name)s"
msgstr "Schichtkonto-Protokoll für: %(name)s"
-#: shifts/views/views.py:209
+#: shifts/views/views.py:211
#, fuzzy, python-format
#| msgid "Create manual shift account entry for: %(link)s"
msgid "Create manual shift account entry for: %(link)s"
msgstr "Erzeuge Schicht-Kontoeintrag für: %(link)s"
-#: shifts/views/views.py:402
+#: shifts/views/views.py:369
+#, fuzzy
+#| msgid "Shift attendance cancelled"
+msgid "Shift template was successfully cancelled"
+msgstr "Schicht"
+
+#: shifts/views/views.py:445
msgid "Frozen statuses updated."
msgstr ""
-#: shifts/views/views.py:467
+#: shifts/views/views.py:510
#, python-format
msgid "Please select either %(shift_template_group)s and/or weekdays, or alternatively %(shift_templates)s."
msgstr ""
@@ -6625,9 +6667,6 @@ msgstr "%(name)s hat an dem Willkommenstreffen noch nicht teilgenommen. Stelle s
#~ " Das Mitgliederbüro\n"
#~ " "
-#~ msgid "Shift attendance cancelled"
-#~ msgstr "Schicht"
-
#~ msgid "Buy"
#~ msgstr "Kaufen"