From 54bcc30a0559ba7b1c30f8f40f330b2a35f023b0 Mon Sep 17 00:00:00 2001 From: Kersten Weise Date: Fri, 16 Feb 2024 10:39:45 +0100 Subject: [PATCH 01/13] Correct some mistakes and included some new translations (Kersten) --- .../translations/locale/de/LC_MESSAGES/django.po | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tapir/translations/locale/de/LC_MESSAGES/django.po b/tapir/translations/locale/de/LC_MESSAGES/django.po index 3ae6668df..a091e0209 100644 --- a/tapir/translations/locale/de/LC_MESSAGES/django.po +++ b/tapir/translations/locale/de/LC_MESSAGES/django.po @@ -45,7 +45,7 @@ msgstr "Es gibt bereits eine Nutzer*in mit diesem Namen." #: accounts/models.py:155 msgid "Displayed name" -msgstr "Angezeigte Name" +msgstr "Angezeigter Name" #: accounts/models.py:160 coop/models.py:60 coop/models.py:431 msgid "Pronouns" @@ -606,7 +606,7 @@ msgstr "Ist eine Firma" #: coop/models.py:51 coop/models.py:422 msgid "Administrative first name" -msgstr "Amtliche Vorname" +msgstr "Amtlicher Vorname" #: coop/models.py:53 coop/models.py:424 msgid "Last name" @@ -614,7 +614,7 @@ msgstr "Nachname" #: coop/models.py:55 coop/models.py:426 msgid "Usage name" -msgstr "Angezeigte Name" +msgstr "Angezeigter Name" #: coop/models.py:61 coop/models.py:432 msgid "Email address" @@ -667,7 +667,7 @@ msgstr "Aktiv" #: coop/models.py:342 msgid "Paused" -msgstr "" +msgstr "Pausiert" #: coop/models.py:395 msgid "Amount paid for a share can't be negative" @@ -692,7 +692,7 @@ msgstr "Anteil(e) bezahlt" #: coop/models.py:518 msgid "Email address must be set." -msgstr "" +msgstr "Email-Adresse muss gesetzt sein." #: coop/models.py:520 msgid "First name must be set." @@ -704,15 +704,15 @@ msgstr "Nachname muss gesetzt sein." #: coop/models.py:524 msgid "Membership agreement must be signed." -msgstr "" +msgstr "Mitgliedsantrag muss unterschrieben sein." #: coop/models.py:526 msgid "Amount of requested shares must be positive." -msgstr "" +msgstr "Die Anzahl der erwünschten Anteile muss positiv sein." #: coop/models.py:528 msgid "Member already created." -msgstr "" +msgstr "Mitglied schon vorhanden." #: coop/models.py:554 msgid "Paying member" From 13cfdea6ccb6b109532d8bf2d4ec73deb4910961 Mon Sep 17 00:00:00 2001 From: Kersten Weise Date: Thu, 23 May 2024 10:48:14 +0200 Subject: [PATCH 02/13] Implementation of the cancellation process in Tapir. --- tapir/coop/admin.py | 3 +- tapir/coop/forms.py | 74 +++++- .../migrations/0042_auto_20240319_1242.py | 31 +++ .../migrations/0043_auto_20240403_2252.py | 25 ++ ...er_resignedmembership_cancellation_date.py | 20 ++ ...er_resignedmembership_cancellation_date.py | 20 ++ .../migrations/0046_auto_20240403_2350.py | 28 +++ .../migrations/0047_auto_20240506_1146.py | 25 ++ ...er_resignedmembership_cancellation_date.py | 20 ++ .../migrations/0049_auto_20240515_1331.py | 46 ++++ .../migrations/0050_auto_20240522_1950.py | 24 ++ tapir/coop/models.py | 70 +++++- tapir/coop/services/ResignMemberService.py | 43 ++++ .../log/create_resignmember_log_entry.html | 4 + .../create_share_ownerships_log_entry.html | 2 +- .../log/update_resignmember_log_entry.html | 7 + .../templates/coop/member_management.html | 4 + .../membership_pause_list.html | 2 +- .../templates/coop/resigned_members_list.html | 83 +++++++ .../coop/resignedmembership_detail.html | 109 ++++++++ .../user_coop_share_ownership_list_tag.html | 5 +- tapir/coop/urls.py | 25 ++ tapir/coop/views/__init__.py | 1 + tapir/coop/views/membership_resign.py | 233 ++++++++++++++++++ tapir/coop/views/shareowner.py | 4 +- .../log/migrations/0007_auto_20240319_1242.py | 32 +++ 26 files changed, 926 insertions(+), 14 deletions(-) create mode 100644 tapir/coop/migrations/0042_auto_20240319_1242.py create mode 100644 tapir/coop/migrations/0043_auto_20240403_2252.py create mode 100644 tapir/coop/migrations/0044_alter_resignedmembership_cancellation_date.py create mode 100644 tapir/coop/migrations/0045_alter_resignedmembership_cancellation_date.py create mode 100644 tapir/coop/migrations/0046_auto_20240403_2350.py create mode 100644 tapir/coop/migrations/0047_auto_20240506_1146.py create mode 100644 tapir/coop/migrations/0048_alter_resignedmembership_cancellation_date.py create mode 100644 tapir/coop/migrations/0049_auto_20240515_1331.py create mode 100644 tapir/coop/migrations/0050_auto_20240522_1950.py create mode 100644 tapir/coop/services/ResignMemberService.py create mode 100644 tapir/coop/templates/coop/log/create_resignmember_log_entry.html create mode 100644 tapir/coop/templates/coop/log/update_resignmember_log_entry.html create mode 100644 tapir/coop/templates/coop/resigned_members_list.html create mode 100644 tapir/coop/templates/coop/resignedmembership_detail.html create mode 100644 tapir/coop/views/membership_resign.py create mode 100644 tapir/log/migrations/0007_auto_20240319_1242.py diff --git a/tapir/coop/admin.py b/tapir/coop/admin.py index f81144f18..f390d0581 100644 --- a/tapir/coop/admin.py +++ b/tapir/coop/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin -from tapir.coop.models import ShareOwnership, DraftUser +from tapir.coop.models import ShareOwnership, DraftUser, ResignedMembership admin.site.register(ShareOwnership) admin.site.register(DraftUser) +admin.site.register(ResignedMembership) \ No newline at end of file diff --git a/tapir/coop/forms.py b/tapir/coop/forms.py index f534085fa..50da580a5 100644 --- a/tapir/coop/forms.py +++ b/tapir/coop/forms.py @@ -1,3 +1,5 @@ +from django.utils import timezone +from dateutil.relativedelta import relativedelta from django import forms from django.core.exceptions import ValidationError from django.forms import DateField, IntegerField @@ -15,8 +17,10 @@ ShareOwner, IncomingPayment, MembershipPause, + ResignedMembership, + TapirUser, ) -from tapir.shifts.forms import ShareOwnerChoiceField +from tapir.shifts.forms import ShareOwnerChoiceField, TapirUserChoiceField from tapir.utils.forms import DateInputTapir, TapirPhoneNumberField @@ -235,8 +239,6 @@ class Meta: "then the fields can be different." ) ) - - class MembershipPauseForm(forms.ModelForm): class Meta: model = MembershipPause @@ -247,3 +249,69 @@ class Meta: } share_owner = ShareOwnerChoiceField() + +class MembershipCancelForm(forms.ModelForm): + now = timezone.now() + already_resigned = ResignedMembership.objects.all() + in_three_years = _(f"Coop buys back share(s)") + # at 31/12/{int(now.strftime("%Y"))+3}') + cancellation_reason = forms.CharField(max_length=1000, widget=forms.Textarea( + attrs={"rows": 2, "placeholder": _("Please not more than 1000 characters.")} + )) + coop_buys_shares_back = forms.BooleanField(label=in_three_years, required=False) + share_owner = ShareOwnerChoiceField() + willing_to_gift_shares_to_coop = forms.BooleanField(label="Willing to gift shares to coop", required=False) + transfering_shares_to = TapirUserChoiceField(required=False, label=_("Transfering share(s) to")) + class Meta: + model = ResignedMembership + fields = ["share_owner", + "cancellation_reason", + "cancellation_date", + "coop_buys_shares_back", + "willing_to_gift_shares_to_coop", + "transfering_shares_to", + "paid_out", + ] + widgets = {"cancellation_date": DateInputTapir()} + + def clean(self): + cleaned_data = super().clean() + share_owner = cleaned_data.get("share_owner") + coop_buys_shares_back = cleaned_data.get("coop_buys_shares_back") + willing_to_gift_shares_to_coop = cleaned_data.get("willing_to_gift_shares_to_coop") + transfering_shares_to = cleaned_data.get("transfering_shares_to") + paid_out = cleaned_data.get("paid_out") + cancellation_date = cleaned_data.get("cancellation_date") + errmsg = _("Please take only one choice.") + + if self.instance.pk is None: + for owner in self.already_resigned: + if owner.share_owner == share_owner: + self.add_error("share_owner", ValidationError( + _("This member is already resigned.") + )) + break + + if coop_buys_shares_back and willing_to_gift_shares_to_coop: + self.add_error("coop_buys_shares_back", errmsg) + self.add_error("willing_to_gift_shares_to_coop", errmsg) + elif transfering_shares_to != None and (coop_buys_shares_back or willing_to_gift_shares_to_coop): + self.add_error("transfering_shares_to", errmsg) + if coop_buys_shares_back: + self.add_error("coop_buys_shares_back", errmsg) + elif willing_to_gift_shares_to_coop: + self.add_error("willing_to_gift_shares_to_coop", errmsg) + if transfering_shares_to is not None: + if transfering_shares_to.share_owner == share_owner: + self.add_error("transfering_shares_to", ValidationError( + _("Sender and receiver of tranfering the share(s) cannot be the same.") + )) + if (transfering_shares_to != None and paid_out) or (willing_to_gift_shares_to_coop and paid_out) : + self.add_error("paid_out", ValidationError(_("Cannot pay out, because shares have been gifted.") + )) + if transfering_shares_to == None and not willing_to_gift_shares_to_coop and not coop_buys_shares_back: + self.add_error("transfering_shares_to", ValidationError(_("Please make a least one choice."))) + self.add_error("willing_to_gift_shares_to_coop", ValidationError(_("Please make a least one choice."))) + self.add_error("coop_buys_shares_back", ValidationError(_("Please make a least one choice."))) + if coop_buys_shares_back: + self.instance.pay_out_day = cancellation_date + relativedelta(day=31, month=12, years=3) \ No newline at end of file diff --git a/tapir/coop/migrations/0042_auto_20240319_1242.py b/tapir/coop/migrations/0042_auto_20240319_1242.py new file mode 100644 index 000000000..e14bb6ff4 --- /dev/null +++ b/tapir/coop/migrations/0042_auto_20240319_1242.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.23 on 2024-03-19 11:42 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('coop', '0041_auto_20231221_1403'), + ] + + operations = [ + migrations.AddField( + model_name='shareownership', + name='cancellation_date', + field=models.DateField(blank=True, default=django.utils.timezone.now, null=True), + ), + migrations.CreateModel( + name='ResignedMembership', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('cancellation_date', models.DateField(blank=True, null=True)), + ('cancellation_reason', models.CharField(max_length=1000)), + ('coop_buys_shares_back', models.BooleanField()), + ('willing_to_gift_shares_to_coop', models.BooleanField()), + ('share_owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coop.shareowner', verbose_name='Shareowner')), + ], + ), + ] diff --git a/tapir/coop/migrations/0043_auto_20240403_2252.py b/tapir/coop/migrations/0043_auto_20240403_2252.py new file mode 100644 index 000000000..ec99a275e --- /dev/null +++ b/tapir/coop/migrations/0043_auto_20240403_2252.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.23 on 2024-04-03 20:52 + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('coop', '0042_auto_20240319_1242'), + ] + + operations = [ + migrations.AddField( + model_name='resignedmembership', + name='paid_out', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='resignedmembership', + name='cancellation_date', + field=models.DateField(blank=True, default=datetime.datetime(2024, 4, 3, 20, 52, 2, 484356, tzinfo=utc), null=True), + ), + ] diff --git a/tapir/coop/migrations/0044_alter_resignedmembership_cancellation_date.py b/tapir/coop/migrations/0044_alter_resignedmembership_cancellation_date.py new file mode 100644 index 000000000..00bccb75f --- /dev/null +++ b/tapir/coop/migrations/0044_alter_resignedmembership_cancellation_date.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.23 on 2024-04-03 21:01 + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('coop', '0043_auto_20240403_2252'), + ] + + operations = [ + migrations.AlterField( + model_name='resignedmembership', + name='cancellation_date', + field=models.DateField(blank=True, default=datetime.datetime(2024, 4, 3, 21, 1, 45, 498750, tzinfo=utc), null=True), + ), + ] diff --git a/tapir/coop/migrations/0045_alter_resignedmembership_cancellation_date.py b/tapir/coop/migrations/0045_alter_resignedmembership_cancellation_date.py new file mode 100644 index 000000000..0bca91855 --- /dev/null +++ b/tapir/coop/migrations/0045_alter_resignedmembership_cancellation_date.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.23 on 2024-04-03 21:35 + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('coop', '0044_alter_resignedmembership_cancellation_date'), + ] + + operations = [ + migrations.AlterField( + model_name='resignedmembership', + name='cancellation_date', + field=models.DateField(blank=True, default=datetime.datetime(2024, 4, 3, 21, 35, 22, 494389, tzinfo=utc), null=True), + ), + ] diff --git a/tapir/coop/migrations/0046_auto_20240403_2350.py b/tapir/coop/migrations/0046_auto_20240403_2350.py new file mode 100644 index 000000000..63175fdcb --- /dev/null +++ b/tapir/coop/migrations/0046_auto_20240403_2350.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.23 on 2024-04-03 21:50 + +import datetime +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('coop', '0045_alter_resignedmembership_cancellation_date'), + ] + + operations = [ + migrations.AddField( + model_name='resignedmembership', + name='transfering_shares_to', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='TapirUser'), + ), + migrations.AlterField( + model_name='resignedmembership', + name='cancellation_date', + field=models.DateField(blank=True, default=datetime.datetime(2024, 4, 3, 21, 50, 43, 829933, tzinfo=utc), null=True), + ), + ] diff --git a/tapir/coop/migrations/0047_auto_20240506_1146.py b/tapir/coop/migrations/0047_auto_20240506_1146.py new file mode 100644 index 000000000..24bf11d0d --- /dev/null +++ b/tapir/coop/migrations/0047_auto_20240506_1146.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.23 on 2024-05-06 09:46 + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('coop', '0046_auto_20240403_2350'), + ] + + operations = [ + migrations.AddField( + model_name='resignedmembership', + name='pay_out_day', + field=models.DateField(null=True), + ), + migrations.AlterField( + model_name='resignedmembership', + name='cancellation_date', + field=models.DateField(blank=True, default=datetime.datetime(2024, 5, 6, 9, 46, 20, 243645, tzinfo=utc), null=True), + ), + ] diff --git a/tapir/coop/migrations/0048_alter_resignedmembership_cancellation_date.py b/tapir/coop/migrations/0048_alter_resignedmembership_cancellation_date.py new file mode 100644 index 000000000..21ac0e07c --- /dev/null +++ b/tapir/coop/migrations/0048_alter_resignedmembership_cancellation_date.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.23 on 2024-05-07 08:42 + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('coop', '0047_auto_20240506_1146'), + ] + + operations = [ + migrations.AlterField( + model_name='resignedmembership', + name='cancellation_date', + field=models.DateField(blank=True, default=datetime.datetime(2024, 5, 7, 8, 42, 36, 563930, tzinfo=utc), null=True), + ), + ] diff --git a/tapir/coop/migrations/0049_auto_20240515_1331.py b/tapir/coop/migrations/0049_auto_20240515_1331.py new file mode 100644 index 000000000..82b15c73d --- /dev/null +++ b/tapir/coop/migrations/0049_auto_20240515_1331.py @@ -0,0 +1,46 @@ +# Generated by Django 3.2.23 on 2024-05-15 11:31 + +import datetime +import django.contrib.postgres.fields.hstore +from django.db import migrations, models +import django.db.models.deletion +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('log', '0007_auto_20240319_1242'), + ('coop', '0048_alter_resignedmembership_cancellation_date'), + ] + + operations = [ + migrations.CreateModel( + name='ResignMembershipCreateLogEntry', + fields=[ + ('logentry_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='log.logentry')), + ('values', django.contrib.postgres.fields.hstore.HStoreField()), + ], + options={ + 'abstract': False, + }, + bases=('log.logentry',), + ), + migrations.CreateModel( + name='ResignMembershipUpdateLogEntry', + fields=[ + ('logentry_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='log.logentry')), + ('old_values', django.contrib.postgres.fields.hstore.HStoreField()), + ('new_values', django.contrib.postgres.fields.hstore.HStoreField()), + ], + options={ + 'abstract': False, + }, + bases=('log.logentry',), + ), + migrations.AlterField( + model_name='resignedmembership', + name='cancellation_date', + field=models.DateField(blank=True, default=datetime.datetime(2024, 5, 15, 11, 31, 18, 181204, tzinfo=utc), null=True), + ), + ] diff --git a/tapir/coop/migrations/0050_auto_20240522_1950.py b/tapir/coop/migrations/0050_auto_20240522_1950.py new file mode 100644 index 000000000..bb24161af --- /dev/null +++ b/tapir/coop/migrations/0050_auto_20240522_1950.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.23 on 2024-05-22 17:50 + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('coop', '0049_auto_20240515_1331'), + ] + + operations = [ + migrations.RemoveField( + model_name='shareownership', + name='cancellation_date', + ), + migrations.AlterField( + model_name='resignedmembership', + name='cancellation_date', + field=models.DateField(blank=True, default=datetime.datetime(2024, 5, 22, 17, 50, 26, 744464, tzinfo=utc), null=True), + ), + ] diff --git a/tapir/coop/models.py b/tapir/coop/models.py index d74d54ffe..08f85c466 100644 --- a/tapir/coop/models.py +++ b/tapir/coop/models.py @@ -23,7 +23,6 @@ from tapir.utils.shortcuts import get_html_link from tapir.utils.user_utils import UserUtils - class ShareOwner(models.Model): """ShareOwner represents a share_owner of a ShareOwnership. @@ -386,7 +385,7 @@ class ShareOwnership(DurationModelMixin, models.Model): max_digits=10, decimal_places=2, ) - + def is_fully_paid(self): return self.amount_paid >= COOP_SHARE_PRICE @@ -719,3 +718,70 @@ def populate( old_frozen=old_frozen, new_frozen=new_frozen, ) + +class ResignedMembership(models.Model): + share_owner = models.ForeignKey( + ShareOwner, on_delete=models.deletion.CASCADE, verbose_name=_("Shareowner") + ) + cancellation_date = models.DateField( + default=timezone.now(), + blank=True, + null=True, + ) + pay_out_day = models.DateField(null=True) + cancellation_reason = models.CharField(max_length = 1000) + coop_buys_shares_back = models.BooleanField() + willing_to_gift_shares_to_coop = models.BooleanField() + transfering_shares_to = models.ForeignKey( + TapirUser, on_delete=models.deletion.CASCADE, verbose_name=_("TapirUser"), null=True, + ) + paid_out = models.BooleanField(default=False) + + class ResignedMemberQuerySet(models.QuerySet): + def with_term(self, search_string: str): + searches = [s for s in search_string.split(" ") if s != ""] + + for search in searches: + word_filter = ( + Q(share_owner__user__usage_name__unaccent__icontains=search) + | Q(share_owner__user__first_name__unaccent__icontains=search) + | Q(share_owner__user__last_name__unaccent__icontains=search) + | Q(share_owner__id__icontains=search) + ) + return self.filter(word_filter) + + objects = ResignedMemberQuerySet.as_manager() + +class ResignMembershipCreateLogEntry(ModelLogEntry): + template_name = "coop/log/create_resignmember_log_entry.html" + + def populate( + self, + actor: User, + model: ResignedMembership, + ): + return super().populate_base( + actor=actor, share_owner=model.share_owner, model=model + ) + + # def get_context_data(self, **kwargs): + # context_data = super().get_context_data() + # context_data["cancellation_reason"] = ResignedMembership.objects.get(pk=self.model.pk).cancellation_reason + # return context_data + +class ResignMembershipUpdateLogEntry(UpdateModelLogEntry): + template_name = "coop/log/update_resignmember_log_entry.html" + + def populate( + self, + old_frozen: dict, + new_frozen: dict, + model: ResignedMembership, + actor: User, + ): + return super().populate_base( + actor=actor, + share_owner=model.share_owner, + old_frozen=old_frozen, + new_frozen=new_frozen, + ) \ No newline at end of file diff --git a/tapir/coop/services/ResignMemberService.py b/tapir/coop/services/ResignMemberService.py new file mode 100644 index 000000000..a723f36a0 --- /dev/null +++ b/tapir/coop/services/ResignMemberService.py @@ -0,0 +1,43 @@ +import datetime + +from tapir.accounts.models import TapirUser +from tapir.coop.models import ResignedMembership, ShareOwnership +from tapir.shifts.models import ShiftAttendanceTemplate, ShiftAttendance + +from tapir.utils.shortcuts import get_timezone_aware_datetime + +class ResignMemberService: + @staticmethod + def update_shifts_and_shares(member: ResignedMembership): + tapir_user: TapirUser = getattr(member.share_owner, "user", None) + if not tapir_user: + print("Couldn't find an existing Tapir user.") + return + + new_end_date = member.cancellation_date + new_end_date = new_end_date.replace(day=31) + new_end_date = new_end_date.replace(month=12) + new_end_date = new_end_date.replace(year=new_end_date.year + 3) + ShareOwnership.objects.filter(share_owner=member.share_owner).update(end_date=new_end_date) + + for attendance_template in ShiftAttendanceTemplate.objects.filter( + user=tapir_user + ): + start_date = get_timezone_aware_datetime( + member.cancellation_date, datetime.time() + ) + attendance_template.cancel_attendances(start_date) + attendance_template.delete() + + attendances = ShiftAttendance.objects.filter( + user=tapir_user, + slot__shift__start_time__gte=start_date, + state=ShiftAttendance.State.PENDING, + ) + + for attendance in attendances: + attendance.state = ShiftAttendance.State.CANCELLED + attendance.save() + + def delete_end_dates(member: ResignedMembership): + ShareOwnership.objects.filter(share_owner=member.share_owner).update(end_date=None) \ No newline at end of file diff --git a/tapir/coop/templates/coop/log/create_resignmember_log_entry.html b/tapir/coop/templates/coop/log/create_resignmember_log_entry.html new file mode 100644 index 000000000..0fa40d7ff --- /dev/null +++ b/tapir/coop/templates/coop/log/create_resignmember_log_entry.html @@ -0,0 +1,4 @@ +{% load i18n %} +{% blocktranslate with member=entry.values.share_owner cancellation_reason=entry.values.cancellation_reason %} +Member resigned for reason: {{ cancellation_reason }} +{% endblocktranslate %} \ No newline at end of file diff --git a/tapir/coop/templates/coop/log/create_share_ownerships_log_entry.html b/tapir/coop/templates/coop/log/create_share_ownerships_log_entry.html index c8844ef28..08e7340bb 100644 --- a/tapir/coop/templates/coop/log/create_share_ownerships_log_entry.html +++ b/tapir/coop/templates/coop/log/create_share_ownerships_log_entry.html @@ -3,4 +3,4 @@ ending {{ entry.end_date }} {% else %} without end date -{% endif %} +{% endif %} \ No newline at end of file diff --git a/tapir/coop/templates/coop/log/update_resignmember_log_entry.html b/tapir/coop/templates/coop/log/update_resignmember_log_entry.html new file mode 100644 index 000000000..05cd8c547 --- /dev/null +++ b/tapir/coop/templates/coop/log/update_resignmember_log_entry.html @@ -0,0 +1,7 @@ +{% load i18n %} +{% translate "Updated resigned membership:" %} +
+{% for change in changes %} + {{ change.0 }}: {{ change.1 }} → {{ change.2 }} +
+{% endfor %} \ No newline at end of file diff --git a/tapir/coop/templates/coop/member_management.html b/tapir/coop/templates/coop/member_management.html index bb9f52853..716945132 100644 --- a/tapir/coop/templates/coop/member_management.html +++ b/tapir/coop/templates/coop/member_management.html @@ -29,6 +29,10 @@
{% translate "Lists" %}
href="{% url 'coop:matching_program_list' %}"> card_giftcard{% translate 'Matching program' %} + + close{% translate 'Resigned members' %} +
diff --git a/tapir/coop/templates/coop/membership_pause/membership_pause_list.html b/tapir/coop/templates/coop/membership_pause/membership_pause_list.html index 6fc7abc72..7a23ef304 100644 --- a/tapir/coop/templates/coop/membership_pause/membership_pause_list.html +++ b/tapir/coop/templates/coop/membership_pause/membership_pause_list.html @@ -31,7 +31,7 @@
{% translate "Membership pauses" %}
+ href="{% url 'coop:membership_pause_create' %}"> add_circle_outline {% translate "Create a new pause" %} diff --git a/tapir/coop/templates/coop/resigned_members_list.html b/tapir/coop/templates/coop/resigned_members_list.html new file mode 100644 index 000000000..76e35bf70 --- /dev/null +++ b/tapir/coop/templates/coop/resigned_members_list.html @@ -0,0 +1,83 @@ +{% extends "core/base.html" %} +{% load render_table from django_tables2 %} +{% load django_bootstrap5 %} +{% load i18n %} +{% load static %} +{% load querystring from django_tables2 %} +{% load export_url from django_tables2 %} +{% load core %} +{% load coop %} +{% load utils %} +{% block title %} + {% translate "List of resigned members" %} +{% endblock title %} +{% block head %} + {{ block.super }} + +{% endblock head %} +{% block content %} + +
+
+
{% translate "List of resigned members" %} ({{ total_of_resigned_members }})
+
+
+ + +
+ + add_circle_outline + {% translate "Resign new member" %} + +
+
+
+
+
+
{% bootstrap_form filter.form %}
+
+ +
+
+
+ + +
  • {% render_table table %}
  • + +
    +{% endblock content %} \ No newline at end of file diff --git a/tapir/coop/templates/coop/resignedmembership_detail.html b/tapir/coop/templates/coop/resignedmembership_detail.html new file mode 100644 index 000000000..45e0da1cd --- /dev/null +++ b/tapir/coop/templates/coop/resignedmembership_detail.html @@ -0,0 +1,109 @@ +{% extends "core/base.html" %} +{% load django_bootstrap5 %} +{% load i18n %} +{% load utils %} +{% load core %} +{% load accounts %} +{% block title %} + {% translate "Resigned member" %}: {% get_display_name_for_viewer object.share_owner request.user %} +{% endblock title %} +{% block content %} + +
    +
    +
    {% translate "Resigned member" %}: {% get_display_name_for_viewer object.share_owner request.user %}
    +
    +
    +
    {% translate "Share owner" %}:
    +
    {% get_display_name_for_viewer object.share_owner request.user %}
    +
    +
    +
    {% translate "Cancellation reason" %}:
    +
    {{ object.cancellation_reason }}
    +
    +
    +
    {% translate "Cancellation date" %}:
    +
    {{ object.cancellation_date }}
    +
    + {% if object.coop_buys_shares_back %} +
    +
    {% translate "Coop buys back share" %}:
    +
    + {% if object.coop_buys_shares_back %} + {% translate "Yes" %} + {% else %} + {% translate "No" %} + {% endif %} +
    +
    + {% elif object.willing_to_gift_shares_to_coop %} +
    +
    {% translate "Willing to gift share to coop" %}:
    +
    + {% if object.willing_to_gift_shares_to_coop %} + {% translate "Yes" %} + {% else %} + {% translate "No" %} + {% endif %} +
    +
    + {% elif object.transfering_shares_to %} +
    +
    {% translate "Transfers share(s) to" %}:
    +
    {% get_display_name_for_viewer object.transfering_shares_to request.user %}
    +
    + {% endif %} +
    +
    {% translate "Paid out" %}:
    +
    + {% if object.paid_out %} + {% translate "Yes" %} + {% else %} + {% translate "No" %} + {% endif %} +
    +
    +
    +
    + +
    + +{% endblock content %} \ No newline at end of file diff --git a/tapir/coop/templates/coop/tags/user_coop_share_ownership_list_tag.html b/tapir/coop/templates/coop/tags/user_coop_share_ownership_list_tag.html index 5d01ecb2b..25c221b55 100644 --- a/tapir/coop/templates/coop/tags/user_coop_share_ownership_list_tag.html +++ b/tapir/coop/templates/coop/tags/user_coop_share_ownership_list_tag.html @@ -176,8 +176,8 @@
    {{ share_owner.willing_to_gift_a_share|yesno:_("Yes,No") }}
    {% if perms.accounts.manage %} -
    -
    + {% csrf_token %} @@ -185,7 +185,6 @@
    send{% translate 'Send membership confirmation email' %} -
    {% endif %} {% else %} {% translate "User is not a cooperative member." %} diff --git a/tapir/coop/urls.py b/tapir/coop/urls.py index f153b4c9a..ca0a44c41 100644 --- a/tapir/coop/urls.py +++ b/tapir/coop/urls.py @@ -75,6 +75,31 @@ views.ShareOwnershipCreateMultipleView.as_view(), name="share_create_multiple", ), + path( + "resign_member/new", + views.ResignShareOwnerCreateView.as_view(), + name="resign_new_membership", + ), + path( + "resign_member/edit/", + views.ResignShareOwnerEditView.as_view(), + name="resign_member_edit", + ), + path( + "resign_member//detail", + views.ResignedShareOwnerDetailView.as_view(), + name="resignedmember_detail", + ), + path( + "resign_member//delete", + views.ResignedShareOwnerRemoveFromListView.as_view(), + name="resign_member_remove", + ), + path( + "resigned_members_list", + views.ResignedShareOwnersList.as_view(), + name="resigned_members_list", + ), path( "member//create_user", views.CreateUserFromShareOwnerView.as_view(), diff --git a/tapir/coop/views/__init__.py b/tapir/coop/views/__init__.py index dcd8e65d2..b54334919 100644 --- a/tapir/coop/views/__init__.py +++ b/tapir/coop/views/__init__.py @@ -4,3 +4,4 @@ from .membership_pause import * from .shareowner import * from .statistics import * +from .membership_resign import * \ No newline at end of file diff --git a/tapir/coop/views/membership_resign.py b/tapir/coop/views/membership_resign.py new file mode 100644 index 000000000..00d724e39 --- /dev/null +++ b/tapir/coop/views/membership_resign.py @@ -0,0 +1,233 @@ +import django_tables2 +import django_filters +from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from django.urls import reverse_lazy +from django.utils.translation import gettext_lazy as _, pgettext_lazy +from django.utils.html import format_html +from django.views.generic import CreateView, UpdateView, DeleteView, DetailView +from django.shortcuts import redirect +from tapir.core.config import TAPIR_TABLE_CLASSES, TAPIR_TABLE_TEMPLATE +from tapir.utils.user_utils import UserUtils +from tapir.core.templatetags.core import tapir_button_link_to_action +from tapir.settings import PERMISSION_COOP_MANAGE + +from django_filters.views import FilterView +from django_tables2 import SingleTableView +from django_tables2.export import ExportMixin + +from tapir.coop.forms import MembershipCancelForm +from tapir.core.views import TapirFormMixin +from tapir.coop.models import ResignedMembership, ResignMembershipCreateLogEntry, ResignMembershipUpdateLogEntry +from tapir.coop.services.ResignMemberService import ResignMemberService +from tapir.log.views import UpdateViewLogMixin +from tapir.log.util import freeze_for_log + +from django.db import transaction + + +class ResignedShareOwnerTable(django_tables2.Table): + class Meta: + model = ResignedMembership + template_name = TAPIR_TABLE_TEMPLATE + fields = [ + "share_owner", + "cancellation_date", + "cancellation_reason", + "paid_out", + "pay_out_day", + ] + sequence = [ + "share_owner", + "cancellation_reason", + "cancellation_date", + "pay_out_day", + "paid_out", + "add_buttons", + ] + order_by = "-cancellation_date" + attrs = {"class": TAPIR_TABLE_CLASSES} + empty_text = "No entries" + default = "No entries" + + cancellation_reason = django_tables2.Column( + attrs = {"td": {"class": "col-4 text-break"}} + ) + pay_out_day = django_tables2.DateColumn( + attrs = {"td": {"class": "" }} + ) + add_buttons = django_tables2.Column( + empty_values=(), + verbose_name="Actions", + orderable=False, + exclude_from_export=True, + default = "No entries", + ) + + def before_render(self, request): + self.request = request + + def render_share_owner(self, record: ResignedMembership): + return UserUtils.build_html_link_for_viewer( + record.share_owner, self.request.user + ) + + def value_share_owner(self, record: ResignedMembership): + return record.share_owner.get_member_number() + + def render_cancellation_reason(self, record: ResignedMembership): + return f"{record.cancellation_reason}" + + def render_pay_out_day(self, record: ResignedMembership): + if record.willing_to_gift_shares_to_coop: + return "Gifted " + chr(8594) + " coop" + elif record.transfering_shares_to != None: + return "Gifted " + chr(8594) + " member" + return record.pay_out_day.strftime("%d/%m/%Y") + + def render_add_buttons(self, value, record: ResignedMembership): + return format_html( + "{}", + #
    {}
    ", + reverse_lazy("coop:resignedmember_detail", args=[record.pk]), + tapir_button_link_to_action(), + format_html("edit"), + # reverse_lazy('coop:resign_member_remove', kwargs={'pk': record.pk}), + # "POST", + # format_html("", + # csrf(html_request)['csrf_token']), + # format_html("", + # tapir_button_link_to_action(), + # ), + ) + +class ResignedMemberFilter(django_filters.FilterSet): + display_name = django_filters.CharFilter( + method="display_name_filter", label=_("Search member")) + paid_out = django_filters.BooleanFilter(widget=django_filters.widgets.BooleanWidget()) + + class Meta: + model = ResignedMembership + fields = ["display_name", "paid_out"] + + @staticmethod + def display_name_filter(queryset: ResignedMembership.ResignedMemberQuerySet, name, value: str): + # if value.isdigit(): + # return queryset.filter(id=int(value)) + return queryset.with_term(value).distinct() + + + + +class ResignedShareOwnersList( + LoginRequiredMixin, + FilterView, + ExportMixin, + SingleTableView, +): + table_class = ResignedShareOwnerTable + model = ResignedMembership + template_name = "coop/resigned_members_list.html" + export_formats = ["csv", "json"] + filterset_class = ResignedMemberFilter + + def get_context_data(self, **kwargs): + context_data = super().get_context_data(**kwargs) + context_data["total_of_resigned_members"] = ResignedMembership.objects.count() + # context_data["resigned_member"] = ResignedMembership.objects.filter(id=self.kwargs["pk"]) + return context_data + +class ResignShareOwnerEditView(LoginRequiredMixin, + PermissionRequiredMixin, + TapirFormMixin, + UpdateViewLogMixin, + UpdateView, +): + model = ResignedMembership + form_class = MembershipCancelForm + permission_required = PERMISSION_COOP_MANAGE + success_url = reverse_lazy("coop:resigned_members_list") + + # def get_form_kwargs(self): + # share_owner = super().get_share_owner() + # kwargs = super(MembershipCancelForm, self).get_form_kwargs() + # kwargs.update({'share_owner': share_owner}) + # return kwargs + + def get_context_data(self, **kwargs): + context_data = super().get_context_data(**kwargs) + # share_owner = super().get_share_owner() + context_data["page_title"] = _("Cancel membership of %(name)s") % { + "name": UserUtils.build_display_name_for_viewer( + person=self.object.share_owner, viewer=self.request.user + ) + } + context_data["card_title"] = _("Cancel membership of %(name)s") % { + "name": UserUtils.build_html_link_for_viewer( + person=self.object.share_owner, viewer=self.request.user + ) + } + return context_data + + def form_valid(self, form): + with transaction.atomic(): + result = super().form_valid(form) + new_frozen = freeze_for_log(form.instance) + if self.old_object_frozen != new_frozen: + ResignMembershipUpdateLogEntry().populate( + old_frozen=self.old_object_frozen, + new_frozen=new_frozen, + model=form.instance, + actor=self.request.user, + ).save() + + return result + +class ResignShareOwnerCreateView(LoginRequiredMixin, + PermissionRequiredMixin, + TapirFormMixin, + CreateView +): + model = ResignedMembership + form_class = MembershipCancelForm + permission_required = PERMISSION_COOP_MANAGE + success_url = reverse_lazy("coop:resigned_members_list") + + def get_context_data(self, **kwargs): + context_data = super().get_context_data(**kwargs) + context_data["page_title"] = _("Resign a new membership") + context_data["card_title"] = context_data["page_title"] + return context_data + + def form_valid(self, form): + with transaction.atomic(): + result = super().form_valid(form) + ResignMemberService.update_shifts_and_shares(form.instance) + ResignMembershipCreateLogEntry().populate( + actor=self.request.user, + model=form.instance, + ).save() + return result + +class ResignedShareOwnerDetailView( + LoginRequiredMixin, PermissionRequiredMixin, DetailView +): + permission_required = PERMISSION_COOP_MANAGE + model = ResignedMembership + + +class ResignedShareOwnerRemoveFromListView( + LoginRequiredMixin, + PermissionRequiredMixin, + DeleteView +): + model = ResignedMembership + permission_required = PERMISSION_COOP_MANAGE + success_url = reverse_lazy("coop:resigned_members_list") + + def delete(self, request, *args, **kwargs): + self.object = self.get_object() + ResignMemberService.delete_end_dates(self.object) + self.object.delete() + response = redirect(self.success_url) + return response + diff --git a/tapir/coop/views/shareowner.py b/tapir/coop/views/shareowner.py index d3f379b7d..9604e1bd3 100644 --- a/tapir/coop/views/shareowner.py +++ b/tapir/coop/views/shareowner.py @@ -205,8 +205,6 @@ def share_ownership_delete(request, pk): share_ownership.delete() return redirect(share_owner) - - class ShareOwnerDetailView( LoginRequiredMixin, PermissionRequiredMixin, generic.DetailView ): @@ -690,7 +688,7 @@ def shift_slot_filter(queryset: ShareOwner.ShareOwnerQuerySet, name, value: str) @staticmethod def display_name_filter(queryset: ShareOwner.ShareOwnerQuerySet, name, value: str): # This is an ugly hack to enable searching by Mitgliedsnummer from the - # one-stop search box in the top right + # one-stop search box in the t op right if value.isdigit(): return queryset.filter(id=int(value)) diff --git a/tapir/log/migrations/0007_auto_20240319_1242.py b/tapir/log/migrations/0007_auto_20240319_1242.py new file mode 100644 index 000000000..7a6ffed74 --- /dev/null +++ b/tapir/log/migrations/0007_auto_20240319_1242.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.23 on 2024-03-19 11:42 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('coop', '0042_auto_20240319_1242'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('log', '0006_alter_emaillogentry_email_content'), + ] + + operations = [ + migrations.AlterField( + model_name='logentry', + name='actor', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='logentry', + name='share_owner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='log_entries', to='coop.shareowner'), + ), + migrations.AlterField( + model_name='logentry', + name='user', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='log_entries', to=settings.AUTH_USER_MODEL), + ), + ] From 18dd28291d071eb32d6d5a899c540ac0d54a113a Mon Sep 17 00:00:00 2001 From: Kersten Weise Date: Thu, 23 May 2024 13:17:44 +0200 Subject: [PATCH 03/13] Little margin and padding improvements for better prints of shifts (regarding safe area) --- tapir/shifts/templates/shifts/shift_day_printable.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tapir/shifts/templates/shifts/shift_day_printable.html b/tapir/shifts/templates/shifts/shift_day_printable.html index b8fe5c2b0..5d87870fd 100644 --- a/tapir/shifts/templates/shifts/shift_day_printable.html +++ b/tapir/shifts/templates/shifts/shift_day_printable.html @@ -11,6 +11,12 @@ width: 210mm; height: 297mm; } + p { + padding: 40px; + } + h1 { + margin-top: 40px; + } } table, th, td { From 4cf16fc8e60c23de8a2781aaaf8feceba93ea4d5 Mon Sep 17 00:00:00 2001 From: Kersten Weise Date: Fri, 31 May 2024 12:25:07 +0200 Subject: [PATCH 04/13] Fixes of the cancellation-process-implementation --- tapir/coop/forms.py | 8 ++--- tapir/coop/models.py | 9 +---- .../templates/coop/resigned_members_list.html | 35 ++++--------------- .../coop/resignedmembership_detail.html | 2 +- tapir/coop/views/membership_resign.py | 10 ++---- 5 files changed, 14 insertions(+), 50 deletions(-) diff --git a/tapir/coop/forms.py b/tapir/coop/forms.py index 50da580a5..9238af0da 100644 --- a/tapir/coop/forms.py +++ b/tapir/coop/forms.py @@ -251,10 +251,8 @@ class Meta: share_owner = ShareOwnerChoiceField() class MembershipCancelForm(forms.ModelForm): - now = timezone.now() - already_resigned = ResignedMembership.objects.all() + already_resigned = ResignedMembership.objects.values("share_owner") in_three_years = _(f"Coop buys back share(s)") - # at 31/12/{int(now.strftime("%Y"))+3}') cancellation_reason = forms.CharField(max_length=1000, widget=forms.Textarea( attrs={"rows": 2, "placeholder": _("Please not more than 1000 characters.")} )) @@ -285,8 +283,8 @@ def clean(self): errmsg = _("Please take only one choice.") if self.instance.pk is None: - for owner in self.already_resigned: - if owner.share_owner == share_owner: + for alreadyResignedMember in self.already_resigned.values("share_owner"): + if alreadyResignedMember['share_owner'] == share_owner.id: self.add_error("share_owner", ValidationError( _("This member is already resigned.") )) diff --git a/tapir/coop/models.py b/tapir/coop/models.py index 08f85c466..13a819e24 100644 --- a/tapir/coop/models.py +++ b/tapir/coop/models.py @@ -726,7 +726,6 @@ class ResignedMembership(models.Model): cancellation_date = models.DateField( default=timezone.now(), blank=True, - null=True, ) pay_out_day = models.DateField(null=True) cancellation_reason = models.CharField(max_length = 1000) @@ -751,7 +750,7 @@ def with_term(self, search_string: str): return self.filter(word_filter) objects = ResignedMemberQuerySet.as_manager() - + class ResignMembershipCreateLogEntry(ModelLogEntry): template_name = "coop/log/create_resignmember_log_entry.html" @@ -763,12 +762,6 @@ def populate( return super().populate_base( actor=actor, share_owner=model.share_owner, model=model ) - - # def get_context_data(self, **kwargs): - # context_data = super().get_context_data() - # context_data["cancellation_reason"] = ResignedMembership.objects.get(pk=self.model.pk).cancellation_reason - # return context_data - class ResignMembershipUpdateLogEntry(UpdateModelLogEntry): template_name = "coop/log/update_resignmember_log_entry.html" diff --git a/tapir/coop/templates/coop/resigned_members_list.html b/tapir/coop/templates/coop/resigned_members_list.html index 76e35bf70..d83561fd7 100644 --- a/tapir/coop/templates/coop/resigned_members_list.html +++ b/tapir/coop/templates/coop/resigned_members_list.html @@ -16,26 +16,6 @@ {% endblock head %} {% block content %} -
    {% translate "List of resigned members" %} ({{ total_of_resigned_members }})
    @@ -61,23 +41,20 @@
    {% translate "List of resigned members" %} ({{ total_of_resigned_members }})

    -
    +
    {% bootstrap_form filter.form %}
    + + clear + {% translate "Clear all filters" %} +
    - -
  • {% render_table table %}
  • -
    {% endblock content %} \ No newline at end of file diff --git a/tapir/coop/templates/coop/resignedmembership_detail.html b/tapir/coop/templates/coop/resignedmembership_detail.html index 45e0da1cd..ec87c613e 100644 --- a/tapir/coop/templates/coop/resignedmembership_detail.html +++ b/tapir/coop/templates/coop/resignedmembership_detail.html @@ -21,7 +21,7 @@
    -

    Are you sure to remove {{ object.share_owner.user.first_name }} {{ object.share_owner.user.last_name }} from the resigned members list and to reactivate the member? This cannot be undone!

    +

    Are you sure you want to remove {{ object.share_owner.user.first_name }} {{ object.share_owner.user.last_name }} from the resigned members list and to reactivate the member? This cannot be undone!

    {% translate "Cancellation date" %}:
    -
    {{ object.cancellation_date }}
    +
    {{ object.cancellation_date|date:"d.m.Y" }}
    {% if object.coop_buys_shares_back %}
    @@ -106,4 +106,4 @@
    {% translate "Resigned member" %}: {% get_display_name_f
    -{% endblock content %} \ No newline at end of file +{% endblock content %} diff --git a/tapir/coop/views/shareowner.py b/tapir/coop/views/shareowner.py index 7c747e5c6..9604e1bd3 100644 --- a/tapir/coop/views/shareowner.py +++ b/tapir/coop/views/shareowner.py @@ -688,7 +688,7 @@ def shift_slot_filter(queryset: ShareOwner.ShareOwnerQuerySet, name, value: str) @staticmethod def display_name_filter(queryset: ShareOwner.ShareOwnerQuerySet, name, value: str): # This is an ugly hack to enable searching by Mitgliedsnummer from the - # one-stop search box in the top right + # one-stop search box in the t op right if value.isdigit(): return queryset.filter(id=int(value)) From 57c0cbab6506fda362b47af0f4dfefa020e81c64 Mon Sep 17 00:00:00 2001 From: Kersten Weise Date: Mon, 3 Jun 2024 18:51:19 +0200 Subject: [PATCH 11/13] removed space between 388-389 in coop/models.py --- tapir/coop/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tapir/coop/models.py b/tapir/coop/models.py index 756a1d643..9d9a5786a 100644 --- a/tapir/coop/models.py +++ b/tapir/coop/models.py @@ -385,7 +385,7 @@ class ShareOwnership(DurationModelMixin, models.Model): max_digits=10, decimal_places=2, ) - + def is_fully_paid(self): return self.amount_paid >= COOP_SHARE_PRICE From 453e942bf5999c0b1aa72a616eb4123ca89dc3b5 Mon Sep 17 00:00:00 2001 From: Kersten Weise Date: Mon, 3 Jun 2024 20:08:42 +0200 Subject: [PATCH 12/13] removed space between 208-209 in models.py --- tapir/coop/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tapir/coop/models.py b/tapir/coop/models.py index 9d9a5786a..d9f1d9025 100644 --- a/tapir/coop/models.py +++ b/tapir/coop/models.py @@ -206,7 +206,7 @@ def clean(self): def get_display_name(self, display_type): return UserUtils.build_display_name(self, display_type) - + def get_html_link(self, display_type): return get_html_link( url=self.get_absolute_url(), text=self.get_display_name(display_type) From d5908e8f20ff76d477041701b90a41ca46d597ea Mon Sep 17 00:00:00 2001 From: Kersten Weise Date: Mon, 3 Jun 2024 20:44:23 +0200 Subject: [PATCH 13/13] now the shareowner.py --- tapir/coop/views/shareowner.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tapir/coop/views/shareowner.py b/tapir/coop/views/shareowner.py index 9604e1bd3..7fd67178d 100644 --- a/tapir/coop/views/shareowner.py +++ b/tapir/coop/views/shareowner.py @@ -205,6 +205,7 @@ def share_ownership_delete(request, pk): share_ownership.delete() return redirect(share_owner) + class ShareOwnerDetailView( LoginRequiredMixin, PermissionRequiredMixin, generic.DetailView ):