Add management commands for semester deadline migration
- add update_semester_deadlines: Update quarterly deadlines to semester-based system - add check_deadlines: Check current deadline status and validation - includes dry-run mode and year filtering options - provides clear progress feedback and summaries Usage on VPS after GitHub Action deployment: python manage.py check_deadlines python manage.py update_semester_deadlines --dry-run python manage.py update_semester_deadlines
This commit is contained in:
77
app/stiftung/management/commands/check_deadlines.py
Normal file
77
app/stiftung/management/commands/check_deadlines.py
Normal file
@@ -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')
|
||||||
@@ -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)')
|
||||||
Reference in New Issue
Block a user