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
This commit is contained in:
133
app/stiftung/management/commands/generate_recurring_payments.py
Normal file
133
app/stiftung/management/commands/generate_recurring_payments.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
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')
|
||||
Reference in New Issue
Block a user