feat(club): archive clubs instead of deleting them

Add Club.archived_at with active()/archived() managers, archive() and
restore(). An archived club stops resolving in ClubTenantMiddleware, so its
subdomain behaves as unknown — archiving is a real deactivation, not a
cosmetic flag — while every row it owns is retained.

There is no hard-delete path, deliberately. A club with any data cannot be
deleted anyway (ClubMembership PROTECTs its Season, and the shop chain
PROTECTs more), and invoices generally must be kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 15:29:10 +02:00
parent 10736fd5ee
commit ebb8bc3db1
5 changed files with 124 additions and 7 deletions

View File

@@ -101,9 +101,7 @@ def members_visible_to(user: User, club: Club) -> QuerySet[Member]:
staff of every team they're staffed on.
"""
if is_club_admin(user, club):
return Member.objects.filter(
Q(member_of__club=club) | Q(team_memberships__team__club=club) | Q(staff_assignments__team__club=club) | Q(roles__club=club)
).distinct()
return Member.objects.filter(Q(member_of__club=club) | Q(team_memberships__team__club=club) | Q(staff_assignments__team__club=club) | Q(roles__club=club)).distinct()
me = Member.objects.filter(user=user).first()
if me is None:
@@ -117,9 +115,7 @@ def members_visible_to(user: User, club: Club) -> QuerySet[Member]:
season = current_season(club)
teams = teams_staffed_by(user, club)
roster = Member.objects.filter(
Q(team_memberships__team__in=teams, team_memberships__season=season) | Q(staff_assignments__team__in=teams, staff_assignments__season=season)
)
roster = Member.objects.filter(Q(team_memberships__team__in=teams, team_memberships__season=season) | Q(staff_assignments__team__in=teams, staff_assignments__season=season))
visible = {me.pk} | set(children.values_list("pk", flat=True)) | set(roster.values_list("pk", flat=True))
return Member.objects.filter(pk__in=visible)