From 149078aaee7a8aaa4a3cff37d69d6944db0df8ea Mon Sep 17 00:00:00 2001 From: Jan Remmer Siebels Date: Tue, 30 Sep 2025 22:11:18 +0200 Subject: [PATCH] Add production diagnostic and fix commands for semester deadlines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - diagnose_deadlines: Check migration status, deadline correctness, and payment patterns - fix_q4_payment_dates: Fix payments with wrong due dates (31.12 → 15.12) - includes dry-run modes and detailed reporting - helps identify why production dates haven't updated Usage on production: python manage.py diagnose_deadlines python manage.py fix_q4_payment_dates --dry-run python manage.py fix_q4_payment_dates --- .../management/commands/diagnose_deadlines.py | 102 ++++++++++++++++++ .../commands/fix_q4_payment_dates.py | 93 ++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 app/stiftung/management/commands/diagnose_deadlines.py create mode 100644 app/stiftung/management/commands/fix_q4_payment_dates.py diff --git a/app/stiftung/management/commands/diagnose_deadlines.py b/app/stiftung/management/commands/diagnose_deadlines.py new file mode 100644 index 0000000..5c81ca2 --- /dev/null +++ b/app/stiftung/management/commands/diagnose_deadlines.py @@ -0,0 +1,102 @@ +from django.core.management.base import BaseCommand +from django.db import connection +from stiftung.models import VierteljahresNachweis, DestinataerUnterstuetzung + + +class Command(BaseCommand): + help = 'Diagnose deadline and payment due date issues on production' + + def handle(self, *args, **options): + self.stdout.write(self.style.SUCCESS('=== PRODUCTION DEADLINE DIAGNOSTIC ===')) + + # Check if migrations have been applied + with connection.cursor() as cursor: + cursor.execute( + "SELECT name FROM django_migrations WHERE app = 'stiftung' AND name LIKE '%deadline%' ORDER BY id" + ) + migration_results = cursor.fetchall() + + self.stdout.write('\n1. Migration Status:') + if migration_results: + for migration in migration_results: + self.stdout.write(f' ✓ {migration[0]}') + else: + self.stdout.write(' ❌ No deadline-related migrations found!') + + # Check quarterly confirmation deadlines + self.stdout.write('\n2. Quarterly Confirmation Deadlines (2025):') + quarterly_records = VierteljahresNachweis.objects.filter(jahr=2025).order_by('quartal', 'destinataer__nachname') + + deadline_summary = {} + for record in quarterly_records[:20]: # Limit to first 20 for readability + quarter = record.quartal + if quarter not in deadline_summary: + deadline_summary[quarter] = [] + deadline_summary[quarter].append(str(record.faelligkeitsdatum)) + + for quarter in sorted(deadline_summary.keys()): + unique_dates = list(set(deadline_summary[quarter])) + expected_dates = { + 1: '2025-03-15', + 2: '2025-06-15', + 3: '2025-09-15', + 4: '2025-12-15' + } + expected = expected_dates[quarter] + status = '✓' if len(unique_dates) == 1 and unique_dates[0] == expected else '❌' + self.stdout.write(f' Q{quarter}: {status} Found: {unique_dates}, Expected: {expected}') + + # Check payment due dates + self.stdout.write('\n3. Payment Due Dates (Recent):') + recent_payments = DestinataerUnterstuetzung.objects.filter( + beschreibung__icontains='Q4/2025' + ).order_by('-faellig_am')[:5] + + if recent_payments: + for payment in recent_payments: + self.stdout.write(f' {payment.destinataer.get_full_name()}: {payment.faellig_am} - {payment.beschreibung}') + else: + self.stdout.write(' No Q4/2025 payments found') + + # Check all 2025 payments pattern + self.stdout.write('\n4. All 2025 Payment Due Date Pattern:') + all_2025_payments = DestinataerUnterstuetzung.objects.filter( + faellig_am__year=2025, + beschreibung__icontains='/' + ).values_list('faellig_am', 'beschreibung').order_by('faellig_am') + + payment_patterns = {} + for payment_date, desc in all_2025_payments: + if 'Q' in desc and '2025' in desc: + quarter_info = desc.split('/')[-1] if '/' in desc else desc + if quarter_info not in payment_patterns: + payment_patterns[quarter_info] = [] + payment_patterns[quarter_info].append(str(payment_date)) + + for quarter_desc in sorted(payment_patterns.keys()): + unique_dates = list(set(payment_patterns[quarter_desc])) + self.stdout.write(f' {quarter_desc}: {unique_dates[:3]}{"..." if len(unique_dates) > 3 else ""}') + + # Provide recommendations + self.stdout.write('\n5. Recommendations:') + + # Check if deadline migration worked + q4_deadlines = VierteljahresNachweis.objects.filter(jahr=2025, quartal=4).values_list('faelligkeitsdatum', flat=True) + unique_q4_deadlines = list(set(q4_deadlines)) + + if len(unique_q4_deadlines) == 1 and str(unique_q4_deadlines[0]) == '2025-12-15': + self.stdout.write(' ✓ Quarterly confirmation deadlines are correct') + else: + self.stdout.write(' ❌ Run: python manage.py update_semester_deadlines') + + # Check if payments need updating + wrong_payments = DestinataerUnterstuetzung.objects.filter( + faellig_am='2025-12-31', + beschreibung__icontains='Q4/2025' + ).count() + + if wrong_payments > 0: + self.stdout.write(f' ❌ {wrong_payments} payments have wrong due date (31.12 instead of 15.12)') + self.stdout.write(' Run: python manage.py fix_q4_payment_dates') + else: + self.stdout.write(' ✓ Payment due dates appear correct') \ No newline at end of file diff --git a/app/stiftung/management/commands/fix_q4_payment_dates.py b/app/stiftung/management/commands/fix_q4_payment_dates.py new file mode 100644 index 0000000..53bba25 --- /dev/null +++ b/app/stiftung/management/commands/fix_q4_payment_dates.py @@ -0,0 +1,93 @@ +from django.core.management.base import BaseCommand +from django.db import transaction +from datetime import date +from stiftung.models import DestinataerUnterstuetzung + + +class Command(BaseCommand): + help = 'Fix Q4 payment due dates from December 31 to December 15' + + def add_arguments(self, parser): + parser.add_argument( + '--dry-run', + action='store_true', + help='Show what would be updated without making changes', + ) + + def handle(self, *args, **options): + dry_run = options['dry_run'] + + if dry_run: + self.stdout.write(self.style.WARNING('DRY RUN MODE - No changes will be made')) + + # Find all Q4/2025 payments with wrong due date (Dec 31 instead of Dec 15) + wrong_payments = DestinataerUnterstuetzung.objects.filter( + faellig_am='2025-12-31', + beschreibung__icontains='Q4/2025' + ) + + total_count = wrong_payments.count() + self.stdout.write(f'Found {total_count} Q4/2025 payments with wrong due date (31.12 instead of 15.12)') + + if total_count == 0: + self.stdout.write(self.style.SUCCESS('No payments need fixing!')) + return + + updated_count = 0 + correct_date = date(2025, 12, 15) # December 15, 2025 + + with transaction.atomic(): + for payment in wrong_payments: + old_date = payment.faellig_am + + if not dry_run: + payment.faellig_am = correct_date + payment.save(update_fields=['faellig_am']) + + updated_count += 1 + + # Show progress for every 5 updates or if verbose + if updated_count % 5 == 0 or options['verbosity'] >= 2: + self.stdout.write( + f' {payment.destinataer.get_full_name()}: ' + f'{old_date} → {correct_date} ({payment.betrag}€)' + ) + + # Summary + if dry_run: + self.stdout.write( + self.style.SUCCESS( + f'DRY RUN: Would update {updated_count} payment due dates from 31.12.2025 to 15.12.2025' + ) + ) + else: + self.stdout.write( + self.style.SUCCESS( + f'Successfully updated {updated_count} payment due dates to December 15, 2025' + ) + ) + + # Also check for other quarters that might have wrong dates + self.stdout.write('\nChecking other quarters for potential issues:') + + quarter_checks = { + 'Q1/2025': ('2025-02-15', '2025-03-15'), + 'Q2/2025': ('2025-05-15', '2025-06-15'), + 'Q3/2025': ('2025-08-15', '2025-09-15'), + } + + for quarter_desc, (old_expected, new_expected) in quarter_checks.items(): + old_date_payments = DestinataerUnterstuetzung.objects.filter( + faellig_am=old_expected, + beschreibung__icontains=quarter_desc + ).count() + + if old_date_payments > 0: + self.stdout.write(f' ⚠️ {quarter_desc}: {old_date_payments} payments still use old date {old_expected}') + self.stdout.write(f' Should be: {new_expected}') + + self.stdout.write('\nSemester-based payment schedule:') + self.stdout.write(' Q1: March 15 (Spring semester)') + self.stdout.write(' Q2: June 15 (Auto-approved with Q1)') + self.stdout.write(' Q3: September 15 (Fall semester)') + self.stdout.write(' Q4: December 15 (Auto-approved with Q3)') \ No newline at end of file