diff --git a/app/stiftung/management/commands/check_deadlines.py b/app/stiftung/management/commands/check_deadlines.py new file mode 100644 index 0000000..2eb7000 --- /dev/null +++ b/app/stiftung/management/commands/check_deadlines.py @@ -0,0 +1,77 @@ +from django.core.management.base import BaseCommand +from collections import defaultdict +from stiftung.models import VierteljahresNachweis + + +class Command(BaseCommand): + help = 'Check current quarterly confirmation deadline status' + + def add_arguments(self, parser): + parser.add_argument( + '--year', + type=int, + help='Check deadlines for specific year (default: current and next year)', + ) + + def handle(self, *args, **options): + from datetime import date + + year_filter = options['year'] + current_year = date.today().year + + if year_filter: + years_to_check = [year_filter] + else: + years_to_check = [current_year, current_year + 1] + + self.stdout.write('Current Quarterly Confirmation Deadline Status') + self.stdout.write('=' * 50) + + for year in years_to_check: + self.stdout.write(f'\nYear: {year}') + self.stdout.write('-' * 20) + + deadlines_by_quarter = defaultdict(set) + status_by_quarter = defaultdict(list) + + records = VierteljahresNachweis.objects.filter(jahr=year).order_by('quartal', 'destinataer__nachname') + + if not records: + self.stdout.write(' No records found for this year') + continue + + for record in records: + deadlines_by_quarter[record.quartal].add(record.faelligkeitsdatum) + status_by_quarter[record.quartal].append(record.status) + + # Show deadline summary + expected_deadlines = { + 1: f'{year}-03-15', # March 15 + 2: f'{year}-06-15', # June 15 + 3: f'{year}-09-15', # September 15 + 4: f'{year}-12-15', # December 15 + } + + for quarter in sorted(deadlines_by_quarter.keys()): + unique_deadlines = list(deadlines_by_quarter[quarter]) + expected = expected_deadlines[quarter] + record_count = len([s for s in status_by_quarter[quarter]]) + + if len(unique_deadlines) == 1 and str(unique_deadlines[0]) == expected: + status_icon = self.style.SUCCESS('✓') + status_text = 'CORRECT' + else: + status_icon = self.style.ERROR('✗') + status_text = 'NEEDS UPDATE' + + self.stdout.write(f' Q{quarter}: {status_icon} {unique_deadlines[0]} (expected: {expected}) - {record_count} records - {status_text}') + + self.stdout.write('\nExpected semester-based deadline structure:') + self.stdout.write(' Q1: March 15 (Primary semester submission)') + self.stdout.write(' Q2: June 15 (Auto-approved when Q1 approved)') + self.stdout.write(' Q3: September 15 (Primary semester submission)') + self.stdout.write(' Q4: December 15 (Auto-approved when Q3 approved)') + + self.stdout.write(f'\nTo update deadlines, run:') + self.stdout.write(f' python manage.py update_semester_deadlines --dry-run # Preview changes') + self.stdout.write(f' python manage.py update_semester_deadlines # Apply changes') \ No newline at end of file diff --git a/app/stiftung/management/commands/update_semester_deadlines.py b/app/stiftung/management/commands/update_semester_deadlines.py new file mode 100644 index 0000000..f005c16 --- /dev/null +++ b/app/stiftung/management/commands/update_semester_deadlines.py @@ -0,0 +1,95 @@ +from django.core.management.base import BaseCommand +from django.db import transaction +from datetime import date +from stiftung.models import VierteljahresNachweis + + +class Command(BaseCommand): + help = 'Update quarterly confirmation deadlines to semester-based system' + + def add_arguments(self, parser): + parser.add_argument( + '--dry-run', + action='store_true', + help='Show what would be updated without making changes', + ) + parser.add_argument( + '--year', + type=int, + help='Only update records for specific year (default: all years)', + ) + + def handle(self, *args, **options): + dry_run = options['dry_run'] + year_filter = options['year'] + + if dry_run: + self.stdout.write( + self.style.WARNING('DRY RUN MODE - No changes will be made') + ) + + # Filter queryset based on year if provided + queryset = VierteljahresNachweis.objects.all() + if year_filter: + queryset = queryset.filter(jahr=year_filter) + self.stdout.write(f'Filtering records for year: {year_filter}') + + updated_count = 0 + total_count = queryset.count() + + self.stdout.write(f'Processing {total_count} quarterly confirmation records...') + + with transaction.atomic(): + for nachweis in queryset: + # Calculate new semester-based deadlines + quarter_deadlines = { + 1: date(nachweis.jahr, 3, 15), # Q1 deadline: March 15 (Spring semester) + 2: date(nachweis.jahr, 6, 15), # Q2 deadline: June 15 (auto-approved) + 3: date(nachweis.jahr, 9, 15), # Q3 deadline: September 15 (Fall semester) + 4: date(nachweis.jahr, 12, 15), # Q4 deadline: December 15 (auto-approved) + } + + new_deadline = quarter_deadlines.get(nachweis.quartal) + + if new_deadline and nachweis.faelligkeitsdatum != new_deadline: + old_deadline = nachweis.faelligkeitsdatum + + if not dry_run: + nachweis.faelligkeitsdatum = new_deadline + nachweis.save(update_fields=['faelligkeitsdatum']) + + updated_count += 1 + + # Show progress for every 10 updates or if verbose + if updated_count % 10 == 0 or options['verbosity'] >= 2: + self.stdout.write( + f' {nachweis.destinataer.get_full_name()}: ' + f'{nachweis.jahr} Q{nachweis.quartal}: ' + f'{old_deadline} → {new_deadline}' + ) + + # Summary + if dry_run: + self.stdout.write( + self.style.SUCCESS( + f'DRY RUN: Would update {updated_count} out of {total_count} records' + ) + ) + else: + self.stdout.write( + self.style.SUCCESS( + f'Successfully updated {updated_count} out of {total_count} records' + ) + ) + + if updated_count == 0: + self.stdout.write( + self.style.WARNING('No records needed updating - all deadlines already correct') + ) + else: + # Show the new deadline structure + self.stdout.write('\nNew semester-based deadline structure:') + self.stdout.write(' Q1: March 15 (Primary semester submission)') + self.stdout.write(' Q2: June 15 (Auto-approved when Q1 approved)') + self.stdout.write(' Q3: September 15 (Primary semester submission)') + self.stdout.write(' Q4: December 15 (Auto-approved when Q3 approved)') \ No newline at end of file