- Apply Black formatting to all Python files in core and stiftung modules - Fix import statement ordering with isort - Ensure all code meets automated quality standards - Resolve CI/CD pipeline formatting failures - Maintain consistent code style across the entire codebase
137 lines
5.0 KiB
Python
137 lines
5.0 KiB
Python
"""
|
|
Management command to generate due recurring support payments.
|
|
This command should be run daily via cron or similar scheduling system.
|
|
"""
|
|
|
|
import logging
|
|
from datetime import timedelta
|
|
|
|
from django.core.management.base import BaseCommand
|
|
from django.utils import timezone
|
|
|
|
from stiftung.models import UnterstuetzungWiederkehrend
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = "Generate due recurring support payments"
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
help="Show what would be generated without actually creating payments",
|
|
)
|
|
parser.add_argument(
|
|
"--days-ahead",
|
|
type=int,
|
|
default=0,
|
|
help="Generate payments that are due within this many days (default: 0 = only today)",
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
dry_run = options["dry_run"]
|
|
days_ahead = options["days_ahead"]
|
|
|
|
heute = timezone.now().date()
|
|
cutoff_date = heute + timedelta(days=days_ahead)
|
|
|
|
self.stdout.write(
|
|
self.style.SUCCESS(
|
|
f'Checking for recurring payments due up to {cutoff_date.strftime("%d.%m.%Y")}...'
|
|
)
|
|
)
|
|
|
|
if dry_run:
|
|
self.stdout.write(
|
|
self.style.WARNING("DRY RUN MODE - No payments will be created")
|
|
)
|
|
|
|
# Get all active recurring payment templates that are due
|
|
templates = UnterstuetzungWiederkehrend.objects.filter(
|
|
aktiv=True, naechste_generierung__lte=cutoff_date
|
|
).select_related("destinataer", "konto")
|
|
|
|
generated_count = 0
|
|
error_count = 0
|
|
|
|
for template in templates:
|
|
try:
|
|
if dry_run:
|
|
self.stdout.write(
|
|
f"Would generate: {template.destinataer.get_full_name()} - "
|
|
f'€{template.betrag} due {template.naechste_generierung.strftime("%d.%m.%Y")}'
|
|
)
|
|
generated_count += 1
|
|
else:
|
|
# Actually generate the payment
|
|
neue_zahlung = template.generiere_naechste_zahlung()
|
|
if neue_zahlung:
|
|
self.stdout.write(
|
|
self.style.SUCCESS(
|
|
f"Generated: {neue_zahlung.destinataer.get_full_name()} - "
|
|
f'€{neue_zahlung.betrag} due {neue_zahlung.faellig_am.strftime("%d.%m.%Y")}'
|
|
)
|
|
)
|
|
generated_count += 1
|
|
logger.info(f"Generated recurring payment: {neue_zahlung.pk}")
|
|
else:
|
|
self.stdout.write(
|
|
self.style.WARNING(
|
|
f"No payment generated for {template.destinataer.get_full_name()} "
|
|
f"(may have reached end date or not yet due)"
|
|
)
|
|
)
|
|
except Exception as e:
|
|
error_count += 1
|
|
self.stdout.write(
|
|
self.style.ERROR(
|
|
f"Error generating payment for {template.destinataer.get_full_name()}: {str(e)}"
|
|
)
|
|
)
|
|
logger.error(
|
|
f"Error generating recurring payment for template {template.pk}: {str(e)}"
|
|
)
|
|
|
|
# Summary
|
|
self.stdout.write("\n" + "=" * 50)
|
|
if dry_run:
|
|
self.stdout.write(
|
|
self.style.SUCCESS(
|
|
f"DRY RUN COMPLETE: {generated_count} payments would be generated"
|
|
)
|
|
)
|
|
else:
|
|
self.stdout.write(
|
|
self.style.SUCCESS(
|
|
f"GENERATION COMPLETE: {generated_count} payments generated"
|
|
)
|
|
)
|
|
|
|
if error_count > 0:
|
|
self.stdout.write(self.style.ERROR(f"{error_count} errors encountered"))
|
|
|
|
# Also check for overdue payments and report them
|
|
from stiftung.models import DestinataerUnterstuetzung
|
|
|
|
overdue_payments = DestinataerUnterstuetzung.objects.filter(
|
|
faellig_am__lt=heute, status__in=["geplant", "faellig"]
|
|
).select_related("destinataer")
|
|
|
|
if overdue_payments.exists():
|
|
self.stdout.write("\n" + "=" * 50)
|
|
self.stdout.write(
|
|
self.style.WARNING(
|
|
f"WARNING: {overdue_payments.count()} overdue payments found:"
|
|
)
|
|
)
|
|
for payment in overdue_payments[:10]: # Limit to first 10
|
|
days_overdue = (heute - payment.faellig_am).days
|
|
self.stdout.write(
|
|
f" - {payment.destinataer.get_full_name()}: €{payment.betrag} "
|
|
f"({days_overdue} days overdue)"
|
|
)
|
|
if overdue_payments.count() > 10:
|
|
self.stdout.write(f" ... and {overdue_payments.count() - 10} more")
|