- 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
134 lines
5.1 KiB
Python
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')
|