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:
@@ -3,60 +3,64 @@ Management command to generate due recurring support payments.
|
||||
This command should be run daily via cron or similar scheduling system.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
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'
|
||||
|
||||
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',
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would be generated without actually creating payments",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--days-ahead',
|
||||
"--days-ahead",
|
||||
type=int,
|
||||
default=0,
|
||||
help='Generate payments that are due within this many days (default: 0 = only today)',
|
||||
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']
|
||||
|
||||
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'))
|
||||
|
||||
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')
|
||||
|
||||
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"Would generate: {template.destinataer.get_full_name()} - "
|
||||
f'€{template.betrag} due {template.naechste_generierung.strftime("%d.%m.%Y")}'
|
||||
)
|
||||
generated_count += 1
|
||||
@@ -66,68 +70,67 @@ class Command(BaseCommand):
|
||||
if neue_zahlung:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f'Generated: {neue_zahlung.destinataer.get_full_name()} - '
|
||||
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}')
|
||||
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)'
|
||||
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)}'
|
||||
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)}')
|
||||
|
||||
logger.error(
|
||||
f"Error generating recurring payment for template {template.pk}: {str(e)}"
|
||||
)
|
||||
|
||||
# Summary
|
||||
self.stdout.write('\n' + '='*50)
|
||||
self.stdout.write("\n" + "=" * 50)
|
||||
if dry_run:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f'DRY RUN COMPLETE: {generated_count} payments would be generated'
|
||||
f"DRY RUN COMPLETE: {generated_count} payments would be generated"
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f'GENERATION COMPLETE: {generated_count} payments generated'
|
||||
f"GENERATION COMPLETE: {generated_count} payments generated"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if error_count > 0:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(f'{error_count} errors encountered')
|
||||
)
|
||||
|
||||
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')
|
||||
|
||||
faellig_am__lt=heute, status__in=["geplant", "faellig"]
|
||||
).select_related("destinataer")
|
||||
|
||||
if overdue_payments.exists():
|
||||
self.stdout.write('\n' + '='*50)
|
||||
self.stdout.write("\n" + "=" * 50)
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
f'WARNING: {overdue_payments.count()} overdue payments found:'
|
||||
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)'
|
||||
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')
|
||||
self.stdout.write(f" ... and {overdue_payments.count() - 10} more")
|
||||
|
||||
@@ -1,93 +1,94 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from stiftung.models import AppConfiguration
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Initialize default app configuration settings'
|
||||
help = "Initialize default app configuration settings"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# Paperless Integration Settings
|
||||
paperless_settings = [
|
||||
{
|
||||
'key': 'paperless_api_url',
|
||||
'display_name': 'Paperless API URL',
|
||||
'description': 'The base URL for your Paperless-NGX API (e.g., http://paperless.example.com:8000)',
|
||||
'value': 'http://192.168.178.167:30070',
|
||||
'default_value': 'http://192.168.178.167:30070',
|
||||
'setting_type': 'url',
|
||||
'category': 'paperless',
|
||||
'order': 1
|
||||
"key": "paperless_api_url",
|
||||
"display_name": "Paperless API URL",
|
||||
"description": "The base URL for your Paperless-NGX API (e.g., http://paperless.example.com:8000)",
|
||||
"value": "http://192.168.178.167:30070",
|
||||
"default_value": "http://192.168.178.167:30070",
|
||||
"setting_type": "url",
|
||||
"category": "paperless",
|
||||
"order": 1,
|
||||
},
|
||||
{
|
||||
'key': 'paperless_api_token',
|
||||
'display_name': 'Paperless API Token',
|
||||
'description': 'The authentication token for Paperless API access',
|
||||
'value': '',
|
||||
'default_value': '',
|
||||
'setting_type': 'text',
|
||||
'category': 'paperless',
|
||||
'order': 2
|
||||
"key": "paperless_api_token",
|
||||
"display_name": "Paperless API Token",
|
||||
"description": "The authentication token for Paperless API access",
|
||||
"value": "",
|
||||
"default_value": "",
|
||||
"setting_type": "text",
|
||||
"category": "paperless",
|
||||
"order": 2,
|
||||
},
|
||||
{
|
||||
'key': 'paperless_destinataere_tag',
|
||||
'display_name': 'Destinatäre Tag Name',
|
||||
'description': 'The tag name used to identify Destinatäre documents in Paperless',
|
||||
'value': 'Stiftung_Destinatäre',
|
||||
'default_value': 'Stiftung_Destinatäre',
|
||||
'setting_type': 'tag',
|
||||
'category': 'paperless',
|
||||
'order': 3
|
||||
"key": "paperless_destinataere_tag",
|
||||
"display_name": "Destinatäre Tag Name",
|
||||
"description": "The tag name used to identify Destinatäre documents in Paperless",
|
||||
"value": "Stiftung_Destinatäre",
|
||||
"default_value": "Stiftung_Destinatäre",
|
||||
"setting_type": "tag",
|
||||
"category": "paperless",
|
||||
"order": 3,
|
||||
},
|
||||
{
|
||||
'key': 'paperless_destinataere_tag_id',
|
||||
'display_name': 'Destinatäre Tag ID',
|
||||
'description': 'The numeric ID of the Destinatäre tag in Paperless',
|
||||
'value': '210',
|
||||
'default_value': '210',
|
||||
'setting_type': 'tag_id',
|
||||
'category': 'paperless',
|
||||
'order': 4
|
||||
"key": "paperless_destinataere_tag_id",
|
||||
"display_name": "Destinatäre Tag ID",
|
||||
"description": "The numeric ID of the Destinatäre tag in Paperless",
|
||||
"value": "210",
|
||||
"default_value": "210",
|
||||
"setting_type": "tag_id",
|
||||
"category": "paperless",
|
||||
"order": 4,
|
||||
},
|
||||
{
|
||||
'key': 'paperless_land_tag',
|
||||
'display_name': 'Land & Pächter Tag Name',
|
||||
'description': 'The tag name used to identify Land and Pächter documents in Paperless',
|
||||
'value': 'Stiftung_Land_und_Pächter',
|
||||
'default_value': 'Stiftung_Land_und_Pächter',
|
||||
'setting_type': 'tag',
|
||||
'category': 'paperless',
|
||||
'order': 5
|
||||
"key": "paperless_land_tag",
|
||||
"display_name": "Land & Pächter Tag Name",
|
||||
"description": "The tag name used to identify Land and Pächter documents in Paperless",
|
||||
"value": "Stiftung_Land_und_Pächter",
|
||||
"default_value": "Stiftung_Land_und_Pächter",
|
||||
"setting_type": "tag",
|
||||
"category": "paperless",
|
||||
"order": 5,
|
||||
},
|
||||
{
|
||||
'key': 'paperless_land_tag_id',
|
||||
'display_name': 'Land & Pächter Tag ID',
|
||||
'description': 'The numeric ID of the Land & Pächter tag in Paperless',
|
||||
'value': '204',
|
||||
'default_value': '204',
|
||||
'setting_type': 'tag_id',
|
||||
'category': 'paperless',
|
||||
'order': 6
|
||||
"key": "paperless_land_tag_id",
|
||||
"display_name": "Land & Pächter Tag ID",
|
||||
"description": "The numeric ID of the Land & Pächter tag in Paperless",
|
||||
"value": "204",
|
||||
"default_value": "204",
|
||||
"setting_type": "tag_id",
|
||||
"category": "paperless",
|
||||
"order": 6,
|
||||
},
|
||||
{
|
||||
'key': 'paperless_admin_tag',
|
||||
'display_name': 'Administration Tag Name',
|
||||
'description': 'The tag name used to identify Administration documents in Paperless',
|
||||
'value': 'Stiftung_Administration',
|
||||
'default_value': 'Stiftung_Administration',
|
||||
'setting_type': 'tag',
|
||||
'category': 'paperless',
|
||||
'order': 7
|
||||
"key": "paperless_admin_tag",
|
||||
"display_name": "Administration Tag Name",
|
||||
"description": "The tag name used to identify Administration documents in Paperless",
|
||||
"value": "Stiftung_Administration",
|
||||
"default_value": "Stiftung_Administration",
|
||||
"setting_type": "tag",
|
||||
"category": "paperless",
|
||||
"order": 7,
|
||||
},
|
||||
{
|
||||
'key': 'paperless_admin_tag_id',
|
||||
'display_name': 'Administration Tag ID',
|
||||
'description': 'The numeric ID of the Administration tag in Paperless',
|
||||
'value': '216',
|
||||
'default_value': '216',
|
||||
'setting_type': 'tag_id',
|
||||
'category': 'paperless',
|
||||
'order': 8
|
||||
}
|
||||
"key": "paperless_admin_tag_id",
|
||||
"display_name": "Administration Tag ID",
|
||||
"description": "The numeric ID of the Administration tag in Paperless",
|
||||
"value": "216",
|
||||
"default_value": "216",
|
||||
"setting_type": "tag_id",
|
||||
"category": "paperless",
|
||||
"order": 8,
|
||||
},
|
||||
]
|
||||
|
||||
created_count = 0
|
||||
@@ -95,26 +96,25 @@ class Command(BaseCommand):
|
||||
|
||||
for setting_data in paperless_settings:
|
||||
setting, created = AppConfiguration.objects.get_or_create(
|
||||
key=setting_data['key'],
|
||||
defaults=setting_data
|
||||
key=setting_data["key"], defaults=setting_data
|
||||
)
|
||||
|
||||
|
||||
if created:
|
||||
created_count += 1
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f'Created setting: {setting.display_name}')
|
||||
self.style.SUCCESS(f"Created setting: {setting.display_name}")
|
||||
)
|
||||
else:
|
||||
# Update existing setting with new defaults if needed
|
||||
if not setting.description:
|
||||
setting.description = setting_data['description']
|
||||
setting.description = setting_data["description"]
|
||||
setting.save()
|
||||
updated_count += 1
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f'Configuration initialized successfully! '
|
||||
f'Created {created_count} new settings, updated {updated_count} existing settings.'
|
||||
f"Configuration initialized successfully! "
|
||||
f"Created {created_count} new settings, updated {updated_count} existing settings."
|
||||
)
|
||||
)
|
||||
self.stdout.write(
|
||||
|
||||
@@ -1,114 +1,116 @@
|
||||
"""
|
||||
Management command to initialize corporate identity settings
|
||||
"""
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from stiftung.models import AppConfiguration
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Initialize corporate identity settings for PDF generation'
|
||||
help = "Initialize corporate identity settings for PDF generation"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
corporate_settings = [
|
||||
{
|
||||
'key': 'corporate_stiftung_name',
|
||||
'display_name': 'Name der Stiftung',
|
||||
'description': 'Der offizielle Name der Stiftung für PDF-Dokumente',
|
||||
'value': 'Stiftung',
|
||||
'default_value': 'Stiftung',
|
||||
'setting_type': 'text',
|
||||
'category': 'corporate',
|
||||
'order': 1
|
||||
"key": "corporate_stiftung_name",
|
||||
"display_name": "Name der Stiftung",
|
||||
"description": "Der offizielle Name der Stiftung für PDF-Dokumente",
|
||||
"value": "Stiftung",
|
||||
"default_value": "Stiftung",
|
||||
"setting_type": "text",
|
||||
"category": "corporate",
|
||||
"order": 1,
|
||||
},
|
||||
{
|
||||
'key': 'corporate_logo_path',
|
||||
'display_name': 'Logo-Pfad',
|
||||
'description': 'Pfad zur Logo-Datei (relativ zu MEDIA_ROOT oder STATIC_ROOT)',
|
||||
'value': '',
|
||||
'default_value': '',
|
||||
'setting_type': 'text',
|
||||
'category': 'corporate',
|
||||
'order': 2
|
||||
"key": "corporate_logo_path",
|
||||
"display_name": "Logo-Pfad",
|
||||
"description": "Pfad zur Logo-Datei (relativ zu MEDIA_ROOT oder STATIC_ROOT)",
|
||||
"value": "",
|
||||
"default_value": "",
|
||||
"setting_type": "text",
|
||||
"category": "corporate",
|
||||
"order": 2,
|
||||
},
|
||||
{
|
||||
'key': 'corporate_primary_color',
|
||||
'display_name': 'Primärfarbe',
|
||||
'description': 'Hauptfarbe für Überschriften und Akzente (Hex-Code)',
|
||||
'value': '#2c3e50',
|
||||
'default_value': '#2c3e50',
|
||||
'setting_type': 'text',
|
||||
'category': 'corporate',
|
||||
'order': 3
|
||||
"key": "corporate_primary_color",
|
||||
"display_name": "Primärfarbe",
|
||||
"description": "Hauptfarbe für Überschriften und Akzente (Hex-Code)",
|
||||
"value": "#2c3e50",
|
||||
"default_value": "#2c3e50",
|
||||
"setting_type": "text",
|
||||
"category": "corporate",
|
||||
"order": 3,
|
||||
},
|
||||
{
|
||||
'key': 'corporate_secondary_color',
|
||||
'display_name': 'Sekundärfarbe',
|
||||
'description': 'Zweitfarbe für Akzente und Details (Hex-Code)',
|
||||
'value': '#3498db',
|
||||
'default_value': '#3498db',
|
||||
'setting_type': 'text',
|
||||
'category': 'corporate',
|
||||
'order': 4
|
||||
"key": "corporate_secondary_color",
|
||||
"display_name": "Sekundärfarbe",
|
||||
"description": "Zweitfarbe für Akzente und Details (Hex-Code)",
|
||||
"value": "#3498db",
|
||||
"default_value": "#3498db",
|
||||
"setting_type": "text",
|
||||
"category": "corporate",
|
||||
"order": 4,
|
||||
},
|
||||
{
|
||||
'key': 'corporate_address_line1',
|
||||
'display_name': 'Adresse Zeile 1',
|
||||
'description': 'Erste Zeile der Stiftungsadresse',
|
||||
'value': '',
|
||||
'default_value': '',
|
||||
'setting_type': 'text',
|
||||
'category': 'corporate',
|
||||
'order': 5
|
||||
"key": "corporate_address_line1",
|
||||
"display_name": "Adresse Zeile 1",
|
||||
"description": "Erste Zeile der Stiftungsadresse",
|
||||
"value": "",
|
||||
"default_value": "",
|
||||
"setting_type": "text",
|
||||
"category": "corporate",
|
||||
"order": 5,
|
||||
},
|
||||
{
|
||||
'key': 'corporate_address_line2',
|
||||
'display_name': 'Adresse Zeile 2',
|
||||
'description': 'Zweite Zeile der Stiftungsadresse (PLZ, Ort)',
|
||||
'value': '',
|
||||
'default_value': '',
|
||||
'setting_type': 'text',
|
||||
'category': 'corporate',
|
||||
'order': 6
|
||||
"key": "corporate_address_line2",
|
||||
"display_name": "Adresse Zeile 2",
|
||||
"description": "Zweite Zeile der Stiftungsadresse (PLZ, Ort)",
|
||||
"value": "",
|
||||
"default_value": "",
|
||||
"setting_type": "text",
|
||||
"category": "corporate",
|
||||
"order": 6,
|
||||
},
|
||||
{
|
||||
'key': 'corporate_phone',
|
||||
'display_name': 'Telefonnummer',
|
||||
'description': 'Telefonnummer der Stiftung',
|
||||
'value': '',
|
||||
'default_value': '',
|
||||
'setting_type': 'text',
|
||||
'category': 'corporate',
|
||||
'order': 7
|
||||
"key": "corporate_phone",
|
||||
"display_name": "Telefonnummer",
|
||||
"description": "Telefonnummer der Stiftung",
|
||||
"value": "",
|
||||
"default_value": "",
|
||||
"setting_type": "text",
|
||||
"category": "corporate",
|
||||
"order": 7,
|
||||
},
|
||||
{
|
||||
'key': 'corporate_email',
|
||||
'display_name': 'E-Mail-Adresse',
|
||||
'description': 'Offizielle E-Mail-Adresse der Stiftung',
|
||||
'value': '',
|
||||
'default_value': '',
|
||||
'setting_type': 'text',
|
||||
'category': 'corporate',
|
||||
'order': 8
|
||||
"key": "corporate_email",
|
||||
"display_name": "E-Mail-Adresse",
|
||||
"description": "Offizielle E-Mail-Adresse der Stiftung",
|
||||
"value": "",
|
||||
"default_value": "",
|
||||
"setting_type": "text",
|
||||
"category": "corporate",
|
||||
"order": 8,
|
||||
},
|
||||
{
|
||||
'key': 'corporate_website',
|
||||
'display_name': 'Website',
|
||||
'description': 'Website der Stiftung',
|
||||
'value': '',
|
||||
'default_value': '',
|
||||
'setting_type': 'url',
|
||||
'category': 'corporate',
|
||||
'order': 9
|
||||
"key": "corporate_website",
|
||||
"display_name": "Website",
|
||||
"description": "Website der Stiftung",
|
||||
"value": "",
|
||||
"default_value": "",
|
||||
"setting_type": "url",
|
||||
"category": "corporate",
|
||||
"order": 9,
|
||||
},
|
||||
{
|
||||
'key': 'corporate_footer_text',
|
||||
'display_name': 'Fußzeilen-Text',
|
||||
'description': 'Text für die Fußzeile in PDF-Dokumenten',
|
||||
'value': 'Dieser Bericht wurde automatisch generiert.',
|
||||
'default_value': 'Dieser Bericht wurde automatisch generiert.',
|
||||
'setting_type': 'text',
|
||||
'category': 'corporate',
|
||||
'order': 10
|
||||
"key": "corporate_footer_text",
|
||||
"display_name": "Fußzeilen-Text",
|
||||
"description": "Text für die Fußzeile in PDF-Dokumenten",
|
||||
"value": "Dieser Bericht wurde automatisch generiert.",
|
||||
"default_value": "Dieser Bericht wurde automatisch generiert.",
|
||||
"setting_type": "text",
|
||||
"category": "corporate",
|
||||
"order": 10,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -117,33 +119,32 @@ class Command(BaseCommand):
|
||||
|
||||
for setting_data in corporate_settings:
|
||||
setting, created = AppConfiguration.objects.get_or_create(
|
||||
key=setting_data['key'],
|
||||
defaults=setting_data
|
||||
key=setting_data["key"], defaults=setting_data
|
||||
)
|
||||
|
||||
|
||||
if created:
|
||||
created_count += 1
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f'Created setting: {setting.display_name}')
|
||||
self.style.SUCCESS(f"Created setting: {setting.display_name}")
|
||||
)
|
||||
else:
|
||||
# Update existing setting with new defaults if needed
|
||||
if not setting.description:
|
||||
setting.description = setting_data['description']
|
||||
setting.description = setting_data["description"]
|
||||
setting.save()
|
||||
updated_count += 1
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f'Corporate identity settings initialized! '
|
||||
f'Created {created_count} new settings, updated {updated_count} existing settings.'
|
||||
f"Corporate identity settings initialized! "
|
||||
f"Created {created_count} new settings, updated {updated_count} existing settings."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if created_count > 0:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
'Please configure your corporate identity settings in '
|
||||
'Administration -> Application Settings before generating PDFs.'
|
||||
"Please configure your corporate identity settings in "
|
||||
"Administration -> Application Settings before generating PDFs."
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,84 +1,93 @@
|
||||
import logging
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from stiftung.models import Land, Verpachtung, Paechter
|
||||
import logging
|
||||
|
||||
from stiftung.models import Land, Paechter, Verpachtung
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Migriert bestehende Verpachtungen in die neue Land-Struktur'
|
||||
help = "Migriert bestehende Verpachtungen in die neue Land-Struktur"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='Zeigt nur an, was gemacht würde, ohne Änderungen zu speichern',
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Zeigt nur an, was gemacht würde, ohne Änderungen zu speichern",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
dry_run = options['dry_run']
|
||||
|
||||
dry_run = options["dry_run"]
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING('DRY RUN - Keine Änderungen werden gespeichert!'))
|
||||
|
||||
self.stdout.write(
|
||||
self.style.WARNING("DRY RUN - Keine Änderungen werden gespeichert!")
|
||||
)
|
||||
|
||||
# Alle aktiven Verpachtungen finden
|
||||
aktive_verpachtungen = Verpachtung.objects.filter(status='aktiv')
|
||||
|
||||
self.stdout.write(f'Gefunden: {aktive_verpachtungen.count()} aktive Verpachtungen')
|
||||
|
||||
aktive_verpachtungen = Verpachtung.objects.filter(status="aktiv")
|
||||
|
||||
self.stdout.write(
|
||||
f"Gefunden: {aktive_verpachtungen.count()} aktive Verpachtungen"
|
||||
)
|
||||
|
||||
migrated_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
|
||||
with transaction.atomic():
|
||||
for verpachtung in aktive_verpachtungen:
|
||||
land = verpachtung.land
|
||||
|
||||
|
||||
# Prüfen ob bereits migriert
|
||||
if land.aktueller_paechter is not None:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
f'Übersprungen: {land} hat bereits einen aktuellen Pächter'
|
||||
f"Übersprungen: {land} hat bereits einen aktuellen Pächter"
|
||||
)
|
||||
)
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
|
||||
# Migration durchführen
|
||||
self.stdout.write(f'Migriere: {land} -> {verpachtung.paechter}')
|
||||
|
||||
self.stdout.write(f"Migriere: {land} -> {verpachtung.paechter}")
|
||||
|
||||
if not dry_run:
|
||||
# Pächter-Daten ins Land übertragen
|
||||
land.aktueller_paechter = verpachtung.paechter
|
||||
land.paechter_name = verpachtung.paechter.get_full_name()
|
||||
land.paechter_anschrift = self._get_paechter_anschrift(verpachtung.paechter)
|
||||
land.paechter_anschrift = self._get_paechter_anschrift(
|
||||
verpachtung.paechter
|
||||
)
|
||||
land.pachtbeginn = verpachtung.pachtbeginn
|
||||
land.pachtende = verpachtung.pachtende
|
||||
land.verlaengerung_klausel = bool(verpachtung.verlaengerung)
|
||||
|
||||
|
||||
# Pachtzins übertragen
|
||||
land.pachtzins_pauschal = verpachtung.pachtzins_jaehrlich
|
||||
|
||||
|
||||
# Verpachtete Fläche aktualisieren (falls nicht gesetzt)
|
||||
if land.verp_flaeche_aktuell == 0:
|
||||
land.verp_flaeche_aktuell = verpachtung.verpachtete_flaeche
|
||||
|
||||
|
||||
land.save()
|
||||
|
||||
|
||||
migrated_count += 1
|
||||
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f'DRY RUN abgeschlossen: {migrated_count} Verpachtungen würden migriert, {skipped_count} übersprungen'
|
||||
f"DRY RUN abgeschlossen: {migrated_count} Verpachtungen würden migriert, {skipped_count} übersprungen"
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f'Migration abgeschlossen: {migrated_count} Verpachtungen migriert, {skipped_count} übersprungen'
|
||||
f"Migration abgeschlossen: {migrated_count} Verpachtungen migriert, {skipped_count} übersprungen"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _get_paechter_anschrift(self, paechter):
|
||||
"""Erstellt eine Anschrift aus den Pächter-Daten"""
|
||||
parts = []
|
||||
@@ -88,5 +97,5 @@ class Command(BaseCommand):
|
||||
parts.append(f"{paechter.plz} {paechter.ort}")
|
||||
elif paechter.ort:
|
||||
parts.append(paechter.ort)
|
||||
|
||||
return '\n'.join(parts) if parts else ''
|
||||
|
||||
return "\n".join(parts) if parts else ""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,111 +1,127 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from stiftung.models import Land, Verpachtung, Paechter, LandAbrechnung
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
from stiftung.models import Land, LandAbrechnung, Paechter, Verpachtung
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Vereinheitlicht Verpachtungen, Land und Abrechnungen zu einem konsistenten System'
|
||||
help = "Vereinheitlicht Verpachtungen, Land und Abrechnungen zu einem konsistenten System"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='Zeigt nur an, was gemacht würde, ohne Änderungen zu speichern',
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Zeigt nur an, was gemacht würde, ohne Änderungen zu speichern",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--create-abrechnungen',
|
||||
action='store_true',
|
||||
help='Erstellt automatisch Abrechnungen aus Verpachtungsdaten',
|
||||
"--create-abrechnungen",
|
||||
action="store_true",
|
||||
help="Erstellt automatisch Abrechnungen aus Verpachtungsdaten",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
dry_run = options['dry_run']
|
||||
create_abrechnungen = options['create_abrechnungen']
|
||||
|
||||
dry_run = options["dry_run"]
|
||||
create_abrechnungen = options["create_abrechnungen"]
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.WARNING('DRY RUN - Keine Änderungen werden gespeichert!'))
|
||||
|
||||
self.stdout.write(
|
||||
self.style.WARNING("DRY RUN - Keine Änderungen werden gespeichert!")
|
||||
)
|
||||
|
||||
# Schritt 1: Alle Verpachtungen analysieren
|
||||
alle_verpachtungen = Verpachtung.objects.all().order_by('land', '-pachtbeginn')
|
||||
self.stdout.write(f'Gefunden: {alle_verpachtungen.count()} Verpachtungen insgesamt')
|
||||
|
||||
alle_verpachtungen = Verpachtung.objects.all().order_by("land", "-pachtbeginn")
|
||||
self.stdout.write(
|
||||
f"Gefunden: {alle_verpachtungen.count()} Verpachtungen insgesamt"
|
||||
)
|
||||
|
||||
land_updates = 0
|
||||
abrechnungen_created = 0
|
||||
|
||||
|
||||
with transaction.atomic():
|
||||
current_land = None
|
||||
|
||||
|
||||
for verpachtung in alle_verpachtungen:
|
||||
land = verpachtung.land
|
||||
|
||||
|
||||
# Für jedes Land nur die neueste aktive Verpachtung als "aktuell" setzen
|
||||
if current_land != land:
|
||||
current_land = land
|
||||
|
||||
|
||||
# Prüfen ob dies die neueste aktive Verpachtung ist
|
||||
if verpachtung.status == 'aktiv' and not land.aktueller_paechter:
|
||||
self.stdout.write(f'Setze aktuelle Verpachtung: {land} -> {verpachtung.paechter}')
|
||||
|
||||
if verpachtung.status == "aktiv" and not land.aktueller_paechter:
|
||||
self.stdout.write(
|
||||
f"Setze aktuelle Verpachtung: {land} -> {verpachtung.paechter}"
|
||||
)
|
||||
|
||||
if not dry_run:
|
||||
# Land-Felder aktualisieren
|
||||
land.aktueller_paechter = verpachtung.paechter
|
||||
land.paechter_name = verpachtung.paechter.get_full_name()
|
||||
land.paechter_anschrift = self._get_paechter_anschrift(verpachtung.paechter)
|
||||
land.paechter_anschrift = self._get_paechter_anschrift(
|
||||
verpachtung.paechter
|
||||
)
|
||||
land.pachtbeginn = verpachtung.pachtbeginn
|
||||
land.pachtende = verpachtung.pachtende
|
||||
land.verlaengerung_klausel = bool(verpachtung.verlaengerung)
|
||||
land.pachtzins_pauschal = verpachtung.pachtzins_jaehrlich
|
||||
|
||||
|
||||
# Verpachtete Fläche synchronisieren
|
||||
land.verp_flaeche_aktuell = verpachtung.verpachtete_flaeche
|
||||
|
||||
|
||||
land.save()
|
||||
land_updates += 1
|
||||
|
||||
|
||||
# Schritt 2: Abrechnungen aus Verpachtungen erstellen (optional)
|
||||
if create_abrechnungen and verpachtung.status == 'aktiv':
|
||||
if create_abrechnungen and verpachtung.status == "aktiv":
|
||||
# Erstelle Abrechnungen für die letzten 3 Jahre
|
||||
current_year = datetime.now().year
|
||||
for jahr in range(current_year - 2, current_year + 1):
|
||||
|
||||
|
||||
# Prüfen ob Abrechnung bereits existiert
|
||||
existing = LandAbrechnung.objects.filter(
|
||||
land=land,
|
||||
abrechnungsjahr=jahr
|
||||
land=land, abrechnungsjahr=jahr
|
||||
).first()
|
||||
|
||||
|
||||
if not existing:
|
||||
self.stdout.write(f'Erstelle Abrechnung: {land} - {jahr}')
|
||||
|
||||
self.stdout.write(f"Erstelle Abrechnung: {land} - {jahr}")
|
||||
|
||||
if not dry_run:
|
||||
abrechnung = LandAbrechnung.objects.create(
|
||||
land=land,
|
||||
abrechnungsjahr=jahr,
|
||||
pacht_vereinnahmt=verpachtung.pachtzins_jaehrlich,
|
||||
bemerkungen=f'Automatisch erstellt aus Verpachtung {verpachtung.vertragsnummer}'
|
||||
bemerkungen=f"Automatisch erstellt aus Verpachtung {verpachtung.vertragsnummer}",
|
||||
)
|
||||
abrechnungen_created += 1
|
||||
|
||||
|
||||
# Zusammenfassung
|
||||
self.stdout.write(self.style.SUCCESS('\n=== MIGRATION ABGESCHLOSSEN ==='))
|
||||
self.stdout.write(self.style.SUCCESS("\n=== MIGRATION ABGESCHLOSSEN ==="))
|
||||
if dry_run:
|
||||
self.stdout.write(f'DRY RUN: {land_updates} Länder würden aktualisiert')
|
||||
self.stdout.write(f"DRY RUN: {land_updates} Länder würden aktualisiert")
|
||||
if create_abrechnungen:
|
||||
self.stdout.write(f'DRY RUN: {abrechnungen_created} Abrechnungen würden erstellt')
|
||||
self.stdout.write(
|
||||
f"DRY RUN: {abrechnungen_created} Abrechnungen würden erstellt"
|
||||
)
|
||||
else:
|
||||
self.stdout.write(f'✓ {land_updates} Länder aktualisiert')
|
||||
self.stdout.write(f"✓ {land_updates} Länder aktualisiert")
|
||||
if create_abrechnungen:
|
||||
self.stdout.write(f'✓ {abrechnungen_created} Abrechnungen erstellt')
|
||||
|
||||
self.stdout.write(f"✓ {abrechnungen_created} Abrechnungen erstellt")
|
||||
|
||||
# Empfehlungen
|
||||
self.stdout.write(self.style.WARNING('\n=== NÄCHSTE SCHRITTE ==='))
|
||||
self.stdout.write('1. Prüfen Sie die migrierten Daten in der Weboberfläche')
|
||||
self.stdout.write('2. Alte Verpachtungs-Views können als "Legacy" markiert werden')
|
||||
self.stdout.write('3. Neue Verpachtungen sollten direkt im Land-Model erstellt werden')
|
||||
|
||||
self.stdout.write(self.style.WARNING("\n=== NÄCHSTE SCHRITTE ==="))
|
||||
self.stdout.write("1. Prüfen Sie die migrierten Daten in der Weboberfläche")
|
||||
self.stdout.write(
|
||||
'2. Alte Verpachtungs-Views können als "Legacy" markiert werden'
|
||||
)
|
||||
self.stdout.write(
|
||||
"3. Neue Verpachtungen sollten direkt im Land-Model erstellt werden"
|
||||
)
|
||||
|
||||
def _get_paechter_anschrift(self, paechter):
|
||||
"""Erstellt eine Anschrift aus den Pächter-Daten"""
|
||||
parts = []
|
||||
@@ -115,5 +131,5 @@ class Command(BaseCommand):
|
||||
parts.append(f"{paechter.plz} {paechter.ort}")
|
||||
elif paechter.ort:
|
||||
parts.append(paechter.ort)
|
||||
|
||||
return '\n'.join(parts) if parts else ''
|
||||
|
||||
return "\n".join(parts) if parts else ""
|
||||
|
||||
Reference in New Issue
Block a user