Files
CCMA/src/ccma/rules/scripts/membership_anniversary.py
T
Marcel PeterkauandClaude Sonnet 5 56a3f5f037 Fix housekeeper false-positive overdue errors and ended-member birthday/anniversary noise
An overdue claim whose reminder had already been sent, with that reminder's own
payment deadline still running, fell through to the plain "überfällig" error
finding whenever reminder_due's escalation check returned None to signal "not
yet time for the next Mahnstufe" -- that None was indistinguishable from "never
handled at all" to the caller, so it looked neglected even though a reminder was
already out. Give that case its own low-priority "reminder_awaiting_deadline"
finding instead of silently falling back to the overdue-error path.

Birthday and membership-anniversary findings also kept firing for members whose
status is "ended", which doesn't make sense once the membership itself is over.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 03:20:18 +02:00

53 lines
1.7 KiB
Python

from ccma.domain.dates import DateValidationError, parse_iso_date
from ccma.rules.api import RuleContext, task
from ccma.rules.scripts._shared import relative_title
RULE_ID = "membership-anniversary"
ORDER = 30
def evaluate(context: RuleContext):
member = context.member
if member.status == "ended":
return []
try:
started_at = parse_iso_date(member.membership_started_at, "Mitglied seit")
except DateValidationError:
return []
if not started_at:
return []
actions = []
for interval in context.settings.anniversary_intervals:
try:
target = interval.target_date(started_at)
except (OverflowError, ValueError):
continue
delta = (target - context.today).days
if (
delta > context.settings.anniversary_days_before
or delta < -context.settings.anniversary_days_after
):
continue
occasion = _anniversary_name(interval)
actions.append(
task(
rule_id=RULE_ID,
member=member,
key_suffix=f"{interval.value}{interval.unit}",
severity="info",
title=relative_title(member.display_name, delta, occasion),
detail=f"Mitglied seit {started_at:%d.%m.%Y}.",
due_date=target,
code="membership_anniversary",
)
)
return actions
def _anniversary_name(interval):
if interval.unit == "D":
return f"{interval.value}-Tage-Mitgliedsjubiläum"
if interval.unit == "M":
return f"{interval.value}-Monats-Mitgliedsjubiläum"
return f"{interval.value}-jähriges Mitgliedsjubiläum"