Format code with Black and isort for CI/CD compliance
- Apply Black formatting to all Python files in core and stiftung modules - Fix import statement ordering with isort - Ensure all code meets automated quality standards - Resolve CI/CD pipeline formatting failures - Maintain consistent code style across the entire codebase
This commit is contained in:
@@ -12,110 +12,116 @@ Usage:
|
||||
python manage.py sync_abrechnungen [--dry-run] [--year YEAR]
|
||||
"""
|
||||
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from decimal import Decimal
|
||||
from datetime import date
|
||||
from stiftung.models import Verpachtung, LandVerpachtung, LandAbrechnung
|
||||
|
||||
from stiftung.models import LandAbrechnung, LandVerpachtung, Verpachtung
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Synchronize existing Verpachtungen with LandAbrechnungen'
|
||||
help = "Synchronize existing Verpachtungen with LandAbrechnungen"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='Show what would be done without making changes',
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would be done without making changes",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--year',
|
||||
"--year",
|
||||
type=int,
|
||||
help='Only sync data for specific year',
|
||||
help="Only sync data for specific year",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--force',
|
||||
action='store_true',
|
||||
help='Force update even if Abrechnungen already exist',
|
||||
"--force",
|
||||
action="store_true",
|
||||
help="Force update even if Abrechnungen already exist",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
dry_run = options['dry_run']
|
||||
target_year = options['year']
|
||||
force = options['force']
|
||||
|
||||
dry_run = options["dry_run"]
|
||||
target_year = options["year"]
|
||||
force = options["force"]
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS('🔄 Starting Abrechnung synchronization...')
|
||||
self.style.SUCCESS("🔄 Starting Abrechnung synchronization...")
|
||||
)
|
||||
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING('📋 DRY RUN MODE - No changes will be made'))
|
||||
|
||||
self.stdout.write(
|
||||
self.style.WARNING("📋 DRY RUN MODE - No changes will be made")
|
||||
)
|
||||
|
||||
# Statistics
|
||||
stats = {
|
||||
'legacy_contracts': 0,
|
||||
'new_contracts': 0,
|
||||
'abrechnungen_created': 0,
|
||||
'abrechnungen_updated': 0,
|
||||
'total_rent_amount': Decimal('0.00'),
|
||||
'years_processed': set(),
|
||||
"legacy_contracts": 0,
|
||||
"new_contracts": 0,
|
||||
"abrechnungen_created": 0,
|
||||
"abrechnungen_updated": 0,
|
||||
"total_rent_amount": Decimal("0.00"),
|
||||
"years_processed": set(),
|
||||
}
|
||||
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# Process Legacy Verpachtungen
|
||||
self.stdout.write('\n📄 Processing Legacy Verpachtungen...')
|
||||
self.stdout.write("\n📄 Processing Legacy Verpachtungen...")
|
||||
legacy_verpachtungen = Verpachtung.objects.all()
|
||||
|
||||
|
||||
for verpachtung in legacy_verpachtungen:
|
||||
stats['legacy_contracts'] += 1
|
||||
stats["legacy_contracts"] += 1
|
||||
years_affected = self._get_affected_years(
|
||||
verpachtung.pachtbeginn,
|
||||
verpachtung.verlaengerung or verpachtung.pachtende,
|
||||
target_year
|
||||
target_year,
|
||||
)
|
||||
|
||||
|
||||
for year in years_affected:
|
||||
stats['years_processed'].add(year)
|
||||
rent_amount = self._calculate_legacy_rent_for_year(verpachtung, year)
|
||||
|
||||
stats["years_processed"].add(year)
|
||||
rent_amount = self._calculate_legacy_rent_for_year(
|
||||
verpachtung, year
|
||||
)
|
||||
|
||||
if not dry_run:
|
||||
created, updated = self._update_abrechnung(
|
||||
verpachtung.land,
|
||||
year,
|
||||
rent_amount,
|
||||
Decimal('0.00'), # No umlage for legacy
|
||||
Decimal("0.00"), # No umlage for legacy
|
||||
f"Legacy-Verpachtung {verpachtung.vertragsnummer}",
|
||||
force
|
||||
force,
|
||||
)
|
||||
if created:
|
||||
stats['abrechnungen_created'] += 1
|
||||
stats["abrechnungen_created"] += 1
|
||||
if updated:
|
||||
stats['abrechnungen_updated'] += 1
|
||||
|
||||
stats['total_rent_amount'] += rent_amount
|
||||
|
||||
stats["abrechnungen_updated"] += 1
|
||||
|
||||
stats["total_rent_amount"] += rent_amount
|
||||
|
||||
self.stdout.write(
|
||||
f" 📊 {verpachtung.vertragsnummer} ({year}): {rent_amount:.2f}€"
|
||||
)
|
||||
|
||||
# Process New LandVerpachtungen
|
||||
self.stdout.write('\n🆕 Processing New LandVerpachtungen...')
|
||||
|
||||
# Process New LandVerpachtungen
|
||||
self.stdout.write("\n🆕 Processing New LandVerpachtungen...")
|
||||
land_verpachtungen = LandVerpachtung.objects.all()
|
||||
|
||||
|
||||
for verpachtung in land_verpachtungen:
|
||||
stats['new_contracts'] += 1
|
||||
stats["new_contracts"] += 1
|
||||
years_affected = self._get_affected_years(
|
||||
verpachtung.pachtbeginn,
|
||||
verpachtung.pachtende,
|
||||
target_year
|
||||
verpachtung.pachtbeginn, verpachtung.pachtende, target_year
|
||||
)
|
||||
|
||||
|
||||
for year in years_affected:
|
||||
stats['years_processed'].add(year)
|
||||
rent_amount = self._calculate_new_rent_for_year(verpachtung, year)
|
||||
umlage_amount = Decimal('0.00') # To be calculated later
|
||||
|
||||
stats["years_processed"].add(year)
|
||||
rent_amount = self._calculate_new_rent_for_year(
|
||||
verpachtung, year
|
||||
)
|
||||
umlage_amount = Decimal("0.00") # To be calculated later
|
||||
|
||||
if not dry_run:
|
||||
created, updated = self._update_abrechnung(
|
||||
verpachtung.land,
|
||||
@@ -123,131 +129,143 @@ class Command(BaseCommand):
|
||||
rent_amount,
|
||||
umlage_amount,
|
||||
f"LandVerpachtung {verpachtung.vertragsnummer}",
|
||||
force
|
||||
force,
|
||||
)
|
||||
if created:
|
||||
stats['abrechnungen_created'] += 1
|
||||
stats["abrechnungen_created"] += 1
|
||||
if updated:
|
||||
stats['abrechnungen_updated'] += 1
|
||||
|
||||
stats['total_rent_amount'] += rent_amount
|
||||
|
||||
stats["abrechnungen_updated"] += 1
|
||||
|
||||
stats["total_rent_amount"] += rent_amount
|
||||
|
||||
self.stdout.write(
|
||||
f" 📊 {verpachtung.vertragsnummer} ({year}): {rent_amount:.2f}€"
|
||||
)
|
||||
|
||||
|
||||
if dry_run:
|
||||
# Rollback transaction in dry run
|
||||
transaction.set_rollback(True)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(f'❌ Error during synchronization: {str(e)}')
|
||||
self.style.ERROR(f"❌ Error during synchronization: {str(e)}")
|
||||
)
|
||||
raise CommandError(f'Synchronization failed: {str(e)}')
|
||||
|
||||
raise CommandError(f"Synchronization failed: {str(e)}")
|
||||
|
||||
# Print summary
|
||||
self.stdout.write('\n' + '='*50)
|
||||
self.stdout.write(self.style.SUCCESS('📈 SYNCHRONIZATION SUMMARY'))
|
||||
self.stdout.write('='*50)
|
||||
self.stdout.write("\n" + "=" * 50)
|
||||
self.stdout.write(self.style.SUCCESS("📈 SYNCHRONIZATION SUMMARY"))
|
||||
self.stdout.write("=" * 50)
|
||||
self.stdout.write(f"Legacy contracts processed: {stats['legacy_contracts']}")
|
||||
self.stdout.write(f"New contracts processed: {stats['new_contracts']}")
|
||||
self.stdout.write(f"Years affected: {', '.join(map(str, sorted(stats['years_processed'])))}")
|
||||
self.stdout.write(
|
||||
f"Years affected: {', '.join(map(str, sorted(stats['years_processed'])))}"
|
||||
)
|
||||
self.stdout.write(f"Abrechnungen created: {stats['abrechnungen_created']}")
|
||||
self.stdout.write(f"Abrechnungen updated: {stats['abrechnungen_updated']}")
|
||||
self.stdout.write(f"Total rent amount: {stats['total_rent_amount']:.2f}€")
|
||||
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING('\n📋 This was a DRY RUN - no changes were saved'))
|
||||
self.stdout.write(
|
||||
self.style.WARNING("\n📋 This was a DRY RUN - no changes were saved")
|
||||
)
|
||||
else:
|
||||
self.stdout.write(self.style.SUCCESS('\n✅ Synchronization completed successfully!'))
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS("\n✅ Synchronization completed successfully!")
|
||||
)
|
||||
|
||||
def _get_affected_years(self, start_date, end_date, target_year=None):
|
||||
"""Get all years affected by a contract"""
|
||||
if not start_date:
|
||||
return []
|
||||
|
||||
|
||||
years = []
|
||||
start_year = start_date.year
|
||||
end_year = end_date.year if end_date else date.today().year
|
||||
|
||||
|
||||
if target_year:
|
||||
if start_year <= target_year <= end_year:
|
||||
return [target_year]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
for year in range(start_year, end_year + 1):
|
||||
years.append(year)
|
||||
|
||||
|
||||
return years
|
||||
|
||||
|
||||
def _calculate_legacy_rent_for_year(self, verpachtung, year):
|
||||
"""Calculate rent for legacy Verpachtung for specific year"""
|
||||
if not verpachtung.pachtzins_jaehrlich or not verpachtung.pachtbeginn:
|
||||
return Decimal('0.00')
|
||||
|
||||
return Decimal("0.00")
|
||||
|
||||
year_start = date(year, 1, 1)
|
||||
year_end = date(year, 12, 31)
|
||||
|
||||
contract_end_date = verpachtung.verlaengerung if verpachtung.verlaengerung else verpachtung.pachtende
|
||||
|
||||
contract_end_date = (
|
||||
verpachtung.verlaengerung
|
||||
if verpachtung.verlaengerung
|
||||
else verpachtung.pachtende
|
||||
)
|
||||
contract_start = max(verpachtung.pachtbeginn, year_start)
|
||||
contract_end = min(contract_end_date or year_end, year_end)
|
||||
|
||||
|
||||
if contract_start > contract_end:
|
||||
return Decimal('0.00')
|
||||
|
||||
return Decimal("0.00")
|
||||
|
||||
days_in_year = (year_end - year_start).days + 1
|
||||
days_active = (contract_end - contract_start).days + 1
|
||||
proportion = Decimal(str(days_active)) / Decimal(str(days_in_year))
|
||||
|
||||
|
||||
return Decimal(str(verpachtung.pachtzins_jaehrlich)) * proportion
|
||||
|
||||
|
||||
def _calculate_new_rent_for_year(self, verpachtung, year):
|
||||
"""Calculate rent for new LandVerpachtung for specific year"""
|
||||
if not verpachtung.pachtzins_pauschal or not verpachtung.pachtbeginn:
|
||||
return Decimal('0.00')
|
||||
|
||||
return Decimal("0.00")
|
||||
|
||||
year_start = date(year, 1, 1)
|
||||
year_end = date(year, 12, 31)
|
||||
|
||||
|
||||
contract_start = max(verpachtung.pachtbeginn, year_start)
|
||||
contract_end = min(verpachtung.pachtende or year_end, year_end)
|
||||
|
||||
|
||||
if contract_start > contract_end:
|
||||
return Decimal('0.00')
|
||||
|
||||
return Decimal("0.00")
|
||||
|
||||
days_in_year = (year_end - year_start).days + 1
|
||||
days_active = (contract_end - contract_start).days + 1
|
||||
proportion = Decimal(str(days_active)) / Decimal(str(days_in_year))
|
||||
|
||||
|
||||
return Decimal(str(verpachtung.pachtzins_pauschal)) * proportion
|
||||
|
||||
def _update_abrechnung(self, land, year, rent_amount, umlage_amount, source_note, force):
|
||||
|
||||
def _update_abrechnung(
|
||||
self, land, year, rent_amount, umlage_amount, source_note, force
|
||||
):
|
||||
"""Update or create Abrechnung for specific land and year"""
|
||||
abrechnung, created = LandAbrechnung.objects.get_or_create(
|
||||
land=land,
|
||||
abrechnungsjahr=year,
|
||||
defaults={
|
||||
'pacht_vereinnahmt': rent_amount,
|
||||
'umlagen_vereinnahmt': umlage_amount,
|
||||
'bemerkungen': f'[{date.today().strftime("%d.%m.%Y")}] Automatisch synchronisiert von {source_note}'
|
||||
}
|
||||
"pacht_vereinnahmt": rent_amount,
|
||||
"umlagen_vereinnahmt": umlage_amount,
|
||||
"bemerkungen": f'[{date.today().strftime("%d.%m.%Y")}] Automatisch synchronisiert von {source_note}',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
updated = False
|
||||
if not created and force:
|
||||
# Update existing
|
||||
abrechnung.pacht_vereinnahmt += rent_amount
|
||||
abrechnung.umlagen_vereinnahmt += umlage_amount
|
||||
|
||||
|
||||
sync_note = f'[{date.today().strftime("%d.%m.%Y")}] Resync: +{rent_amount:.2f}€ von {source_note}'
|
||||
if abrechnung.bemerkungen:
|
||||
abrechnung.bemerkungen += f'\n{sync_note}'
|
||||
abrechnung.bemerkungen += f"\n{sync_note}"
|
||||
else:
|
||||
abrechnung.bemerkungen = sync_note
|
||||
|
||||
|
||||
abrechnung.save()
|
||||
updated = True
|
||||
|
||||
|
||||
return created, updated
|
||||
|
||||
Reference in New Issue
Block a user