Files
stiftung-management-system/app/stiftung/management/commands/generate_recurring_payments.py
Stiftung Development 35ba089a84 fix: configure CI database connection properly
- Add dotenv loading to Django settings
- Update CI workflow to use correct environment variables
- Set POSTGRES_* variables instead of DATABASE_URL
- Add environment variables to all Django management commands
- Fixes CI test failures due to database connection issues
2025-09-06 18:47:23 +02:00

134 lines
5.1 KiB
Python

"""
Management command to generate due recurring support payments.
This command should be run daily via cron or similar scheduling system.
"""
from django.core.management.base import BaseCommand
from django.utils import timezone
from datetime import timedelta
from stiftung.models import UnterstuetzungWiederkehrend
import logging
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')