Checkpoint: management app redesign, onboarding/signup workflow, and events calendar backend

Large uncommitted body of work accumulated across sessions on this branch --
committing as a checkpoint so it's tracked and future worktree-isolated agents
see the real codebase instead of a stale ancestor commit. Covers the
management app's dedicated Tailwind theme and templates, the club onboarding
requirement/signup workflow (club/services/onboarding.py, requirement/status
models, sign-up dashboard), fee/status auto-activation decoupling, referee
management, and the new events calendar grid service layer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
2026-08-19 23:34:43 +02:00
parent bff685966d
commit adf1120358
157 changed files with 20342 additions and 4008 deletions

View File

@@ -151,3 +151,38 @@ class Maintenance(UUIDModel):
maintenance.save()
return maintenance
class JobRun(UUIDModel):
"""One execution of a scheduled platform job -- see features/jobs.py for the registry
of what each job is, and rosterchief/settings.CELERY_BEAT_SCHEDULE for when it runs.
Written entirely by the Celery signal handlers in features/signals.py: individual tasks
(billing/tasks.py, club/tasks.py, events/tasks.py) don't touch this model, so a task
that raises still gets a row -- the signal fires regardless of how the task ended.
"""
class Status(models.TextChoices):
STARTED = "started", _("Started")
SUCCESS = "success", _("Success")
FAILURE = "failure", _("Failed")
task_id = models.CharField(_("task id"), max_length=255, unique=True)
name = models.CharField(_("task name"), max_length=255, help_text=_("Dotted Celery task name, e.g. billing.tasks.renew_subscriptions."))
status = models.CharField(_("status"), max_length=10, choices=Status.choices, default=Status.STARTED)
started_at = models.DateTimeField(_("started at"))
finished_at = models.DateTimeField(_("finished at"), null=True, blank=True)
detail = models.TextField(_("detail"), blank=True, help_text=_("What the task returned, on success."))
error = models.TextField(_("error"), blank=True, help_text=_("What the task raised, on failure."))
class Meta:
verbose_name = _("job run")
verbose_name_plural = _("job runs")
ordering = ["-started_at"]
def __str__(self):
return f"{self.name} · {self.started_at:%Y-%m-%d %H:%M}"
@property
def duration(self):
return None if self.finished_at is None else self.finished_at - self.started_at