""" 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")