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:
@@ -1,4 +1,3 @@
|
|||||||
from .celery import app as celery
|
from .celery import app as celery
|
||||||
|
|
||||||
__all__ = ("celery",)
|
__all__ = ("celery",)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
from django.core.asgi import get_asgi_application
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
|
||||||
application = get_asgi_application()
|
application = get_asgi_application()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from celery import Celery
|
from celery import Celery
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
|
||||||
@@ -6,5 +7,3 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
|
|||||||
app = Celery("core")
|
app = Celery("core")
|
||||||
app.config_from_object("django.conf:settings", namespace="CELERY")
|
app.config_from_object("django.conf:settings", namespace="CELERY")
|
||||||
app.autodiscover_tasks()
|
app.autodiscover_tasks()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
# Load environment variables from .env file
|
# Load environment variables from .env file
|
||||||
@@ -13,59 +14,59 @@ ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "*").split(",")
|
|||||||
|
|
||||||
# CSRF settings for localhost development
|
# CSRF settings for localhost development
|
||||||
CSRF_TRUSTED_ORIGINS = [
|
CSRF_TRUSTED_ORIGINS = [
|
||||||
'http://localhost:8081',
|
"http://localhost:8081",
|
||||||
'http://127.0.0.1:8081',
|
"http://127.0.0.1:8081",
|
||||||
]
|
]
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.admin',
|
"django.contrib.admin",
|
||||||
'django.contrib.auth',
|
"django.contrib.auth",
|
||||||
'django.contrib.contenttypes',
|
"django.contrib.contenttypes",
|
||||||
'django.contrib.sessions',
|
"django.contrib.sessions",
|
||||||
'django.contrib.messages',
|
"django.contrib.messages",
|
||||||
'django.contrib.staticfiles',
|
"django.contrib.staticfiles",
|
||||||
'django.contrib.humanize',
|
"django.contrib.humanize",
|
||||||
'rest_framework',
|
"rest_framework",
|
||||||
'stiftung',
|
"stiftung",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
"django.middleware.security.SecurityMiddleware",
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
'django.middleware.common.CommonMiddleware',
|
"django.middleware.common.CommonMiddleware",
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
'stiftung.middleware.AuditMiddleware', # Audit logging middleware
|
"stiftung.middleware.AuditMiddleware", # Audit logging middleware
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'core.urls'
|
ROOT_URLCONF = "core.urls"
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
'DIRS': [BASE_DIR / 'templates'],
|
"DIRS": [BASE_DIR / "templates"],
|
||||||
'APP_DIRS': True,
|
"APP_DIRS": True,
|
||||||
'OPTIONS': {
|
"OPTIONS": {
|
||||||
'context_processors': [
|
"context_processors": [
|
||||||
'django.template.context_processors.debug',
|
"django.template.context_processors.debug",
|
||||||
'django.template.context_processors.request',
|
"django.template.context_processors.request",
|
||||||
'django.contrib.auth.context_processors.auth',
|
"django.contrib.auth.context_processors.auth",
|
||||||
'django.contrib.messages.context_processors.messages',
|
"django.contrib.messages.context_processors.messages",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
WSGI_APPLICATION = 'core.wsgi.application'
|
WSGI_APPLICATION = "core.wsgi.application"
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
"default": {
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
'NAME': os.getenv('POSTGRES_DB', 'stiftung'),
|
"NAME": os.getenv("POSTGRES_DB", "stiftung"),
|
||||||
'USER': os.getenv('POSTGRES_USER', 'stiftung'),
|
"USER": os.getenv("POSTGRES_USER", "stiftung"),
|
||||||
'PASSWORD': os.getenv('POSTGRES_PASSWORD', 'stiftungpass'),
|
"PASSWORD": os.getenv("POSTGRES_PASSWORD", "stiftungpass"),
|
||||||
'HOST': os.getenv('DB_HOST', 'db'),
|
"HOST": os.getenv("DB_HOST", "db"),
|
||||||
'PORT': os.getenv('DB_PORT', '5432'),
|
"PORT": os.getenv("DB_PORT", "5432"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,17 +75,17 @@ TIME_ZONE = os.getenv("TIME_ZONE", "Europe/Berlin")
|
|||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
STATIC_URL = "static/"
|
||||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
STATIC_ROOT = BASE_DIR / "staticfiles"
|
||||||
|
|
||||||
# Additional locations of static files
|
# Additional locations of static files
|
||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
BASE_DIR / "static",
|
BASE_DIR / "static",
|
||||||
]
|
]
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
MEDIA_URL = '/media/'
|
MEDIA_URL = "/media/"
|
||||||
MEDIA_ROOT = BASE_DIR / 'media'
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
# Celery
|
# Celery
|
||||||
CELERY_BROKER_URL = os.getenv("REDIS_URL", "redis://redis:6379/0")
|
CELERY_BROKER_URL = os.getenv("REDIS_URL", "redis://redis:6379/0")
|
||||||
@@ -101,13 +102,13 @@ PAPERLESS_LAND_TAG_ID = os.getenv("PAPERLESS_LAND_TAG_ID", "204")
|
|||||||
PAPERLESS_ADMIN_TAG_ID = os.getenv("PAPERLESS_ADMIN_TAG_ID", "216")
|
PAPERLESS_ADMIN_TAG_ID = os.getenv("PAPERLESS_ADMIN_TAG_ID", "216")
|
||||||
|
|
||||||
# Authentication
|
# Authentication
|
||||||
LOGIN_URL = '/login/'
|
LOGIN_URL = "/login/"
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = "/"
|
||||||
LOGOUT_REDIRECT_URL = '/login/'
|
LOGOUT_REDIRECT_URL = "/login/"
|
||||||
|
|
||||||
# Gramps integration
|
# Gramps integration
|
||||||
GRAMPS_URL = os.environ.get('GRAMPS_URL', 'http://grampsweb:80')
|
GRAMPS_URL = os.environ.get("GRAMPS_URL", "http://grampsweb:80")
|
||||||
GRAMPS_API_TOKEN = os.environ.get('GRAMPS_API_TOKEN', '')
|
GRAMPS_API_TOKEN = os.environ.get("GRAMPS_API_TOKEN", "")
|
||||||
GRAMPS_STIFTER_IDS = os.environ.get('GRAMPS_STIFTER_IDS', '') # comma-separated
|
GRAMPS_STIFTER_IDS = os.environ.get("GRAMPS_STIFTER_IDS", "") # comma-separated
|
||||||
GRAMPS_USERNAME = os.environ.get('GRAMPS_USERNAME', '')
|
GRAMPS_USERNAME = os.environ.get("GRAMPS_USERNAME", "")
|
||||||
GRAMPS_PASSWORD = os.environ.get('GRAMPS_PASSWORD', '')
|
GRAMPS_PASSWORD = os.environ.get("GRAMPS_PASSWORD", "")
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
from django.contrib import admin
|
|
||||||
from django.urls import path, include
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
from django.contrib import admin
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
from stiftung.views import home
|
from stiftung.views import home
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include('stiftung.urls')),
|
path("", include("stiftung.urls")),
|
||||||
path('admin/', admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
|
|
||||||
# Authentication URLs
|
# Authentication URLs
|
||||||
path('login/', auth_views.LoginView.as_view(template_name='registration/login.html'), name='login'),
|
path(
|
||||||
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
|
"login/",
|
||||||
|
auth_views.LoginView.as_view(template_name="registration/login.html"),
|
||||||
|
name="login",
|
||||||
|
),
|
||||||
|
path("logout/", auth_views.LogoutView.as_view(), name="logout"),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class StiftungConfig(AppConfig):
|
class StiftungConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
name = 'stiftung'
|
name = "stiftung"
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ Provides functions to log user actions throughout the application
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from django.utils import timezone
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from stiftung.models import AuditLog
|
from stiftung.models import AuditLog
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
@@ -13,18 +15,20 @@ User = get_user_model()
|
|||||||
|
|
||||||
def get_client_ip(request):
|
def get_client_ip(request):
|
||||||
"""Extract the client IP address from the request"""
|
"""Extract the client IP address from the request"""
|
||||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
||||||
if x_forwarded_for:
|
if x_forwarded_for:
|
||||||
ip = x_forwarded_for.split(',')[0]
|
ip = x_forwarded_for.split(",")[0]
|
||||||
else:
|
else:
|
||||||
ip = request.META.get('REMOTE_ADDR')
|
ip = request.META.get("REMOTE_ADDR")
|
||||||
return ip
|
return ip
|
||||||
|
|
||||||
|
|
||||||
def log_action(request, action, entity_type, entity_id, entity_name, description, changes=None):
|
def log_action(
|
||||||
|
request, action, entity_type, entity_id, entity_name, description, changes=None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Log a user action to the audit log
|
Log a user action to the audit log
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: Django request object
|
request: Django request object
|
||||||
action: Action type (create, update, delete, etc.)
|
action: Action type (create, update, delete, etc.)
|
||||||
@@ -35,28 +39,28 @@ def log_action(request, action, entity_type, entity_id, entity_name, description
|
|||||||
changes: Dictionary of field changes (optional)
|
changes: Dictionary of field changes (optional)
|
||||||
"""
|
"""
|
||||||
user = request.user if request.user.is_authenticated else None
|
user = request.user if request.user.is_authenticated else None
|
||||||
username = user.username if user else 'Anonymous'
|
username = user.username if user else "Anonymous"
|
||||||
|
|
||||||
# Get request metadata
|
# Get request metadata
|
||||||
ip_address = get_client_ip(request)
|
ip_address = get_client_ip(request)
|
||||||
user_agent = request.META.get('HTTP_USER_AGENT', '')
|
user_agent = request.META.get("HTTP_USER_AGENT", "")
|
||||||
session_key = request.session.session_key if hasattr(request, 'session') else ''
|
session_key = request.session.session_key if hasattr(request, "session") else ""
|
||||||
|
|
||||||
# Create audit log entry
|
# Create audit log entry
|
||||||
audit_entry = AuditLog.objects.create(
|
audit_entry = AuditLog.objects.create(
|
||||||
user=user,
|
user=user,
|
||||||
username=username,
|
username=username,
|
||||||
action=action,
|
action=action,
|
||||||
entity_type=entity_type,
|
entity_type=entity_type,
|
||||||
entity_id=str(entity_id) if entity_id else '',
|
entity_id=str(entity_id) if entity_id else "",
|
||||||
entity_name=entity_name,
|
entity_name=entity_name,
|
||||||
description=description,
|
description=description,
|
||||||
changes=changes,
|
changes=changes,
|
||||||
ip_address=ip_address,
|
ip_address=ip_address,
|
||||||
user_agent=user_agent[:500], # Truncate to avoid very long user agents
|
user_agent=user_agent[:500], # Truncate to avoid very long user agents
|
||||||
session_key=session_key
|
session_key=session_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
return audit_entry
|
return audit_entry
|
||||||
|
|
||||||
|
|
||||||
@@ -64,14 +68,14 @@ def log_create(request, entity_type, entity_id, entity_name, description=None):
|
|||||||
"""Log entity creation"""
|
"""Log entity creation"""
|
||||||
if not description:
|
if not description:
|
||||||
description = f"Neue {entity_type.replace('_', ' ').title()} '{entity_name}' wurde erstellt"
|
description = f"Neue {entity_type.replace('_', ' ').title()} '{entity_name}' wurde erstellt"
|
||||||
|
|
||||||
return log_action(
|
return log_action(
|
||||||
request=request,
|
request=request,
|
||||||
action='create',
|
action="create",
|
||||||
entity_type=entity_type,
|
entity_type=entity_type,
|
||||||
entity_id=entity_id,
|
entity_id=entity_id,
|
||||||
entity_name=entity_name,
|
entity_name=entity_name,
|
||||||
description=description
|
description=description,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -81,60 +85,78 @@ def log_update(request, entity_type, entity_id, entity_name, changes, descriptio
|
|||||||
changed_fields = list(changes.keys()) if changes else []
|
changed_fields = list(changes.keys()) if changes else []
|
||||||
fields_str = ", ".join(changed_fields)
|
fields_str = ", ".join(changed_fields)
|
||||||
description = f"{entity_type.replace('_', ' ').title()} '{entity_name}' wurde aktualisiert: {fields_str}"
|
description = f"{entity_type.replace('_', ' ').title()} '{entity_name}' wurde aktualisiert: {fields_str}"
|
||||||
|
|
||||||
return log_action(
|
return log_action(
|
||||||
request=request,
|
request=request,
|
||||||
action='update',
|
action="update",
|
||||||
entity_type=entity_type,
|
entity_type=entity_type,
|
||||||
entity_id=entity_id,
|
entity_id=entity_id,
|
||||||
entity_name=entity_name,
|
entity_name=entity_name,
|
||||||
description=description,
|
description=description,
|
||||||
changes=changes
|
changes=changes,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def log_delete(request, entity_type, entity_id, entity_name, description=None):
|
def log_delete(request, entity_type, entity_id, entity_name, description=None):
|
||||||
"""Log entity deletion"""
|
"""Log entity deletion"""
|
||||||
if not description:
|
if not description:
|
||||||
description = f"{entity_type.replace('_', ' ').title()} '{entity_name}' wurde gelöscht"
|
description = (
|
||||||
|
f"{entity_type.replace('_', ' ').title()} '{entity_name}' wurde gelöscht"
|
||||||
|
)
|
||||||
|
|
||||||
return log_action(
|
return log_action(
|
||||||
request=request,
|
request=request,
|
||||||
action='delete',
|
action="delete",
|
||||||
entity_type=entity_type,
|
entity_type=entity_type,
|
||||||
entity_id=entity_id,
|
entity_id=entity_id,
|
||||||
entity_name=entity_name,
|
entity_name=entity_name,
|
||||||
description=description
|
description=description,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def log_link(request, entity_type, entity_id, entity_name, target_type, target_name, description=None):
|
def log_link(
|
||||||
|
request,
|
||||||
|
entity_type,
|
||||||
|
entity_id,
|
||||||
|
entity_name,
|
||||||
|
target_type,
|
||||||
|
target_name,
|
||||||
|
description=None,
|
||||||
|
):
|
||||||
"""Log entity linking"""
|
"""Log entity linking"""
|
||||||
if not description:
|
if not description:
|
||||||
description = f"{entity_type.replace('_', ' ').title()} '{entity_name}' wurde mit {target_type.replace('_', ' ')} '{target_name}' verknüpft"
|
description = f"{entity_type.replace('_', ' ').title()} '{entity_name}' wurde mit {target_type.replace('_', ' ')} '{target_name}' verknüpft"
|
||||||
|
|
||||||
return log_action(
|
return log_action(
|
||||||
request=request,
|
request=request,
|
||||||
action='link',
|
action="link",
|
||||||
entity_type=entity_type,
|
entity_type=entity_type,
|
||||||
entity_id=entity_id,
|
entity_id=entity_id,
|
||||||
entity_name=entity_name,
|
entity_name=entity_name,
|
||||||
description=description
|
description=description,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def log_unlink(request, entity_type, entity_id, entity_name, target_type, target_name, description=None):
|
def log_unlink(
|
||||||
|
request,
|
||||||
|
entity_type,
|
||||||
|
entity_id,
|
||||||
|
entity_name,
|
||||||
|
target_type,
|
||||||
|
target_name,
|
||||||
|
description=None,
|
||||||
|
):
|
||||||
"""Log entity unlinking"""
|
"""Log entity unlinking"""
|
||||||
if not description:
|
if not description:
|
||||||
description = f"Verknüpfung zwischen {entity_type.replace('_', ' ').title()} '{entity_name}' und {target_type.replace('_', ' ')} '{target_name}' wurde entfernt"
|
description = f"Verknüpfung zwischen {entity_type.replace('_', ' ').title()} '{entity_name}' und {target_type.replace('_', ' ')} '{target_name}' wurde entfernt"
|
||||||
|
|
||||||
return log_action(
|
return log_action(
|
||||||
request=request,
|
request=request,
|
||||||
action='unlink',
|
action="unlink",
|
||||||
entity_type=entity_type,
|
entity_type=entity_type,
|
||||||
entity_id=entity_id,
|
entity_id=entity_id,
|
||||||
entity_name=entity_name,
|
entity_name=entity_name,
|
||||||
description=description
|
description=description,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -143,51 +165,54 @@ def log_system_action(request, action, description, details=None):
|
|||||||
return log_action(
|
return log_action(
|
||||||
request=request,
|
request=request,
|
||||||
action=action,
|
action=action,
|
||||||
entity_type='system',
|
entity_type="system",
|
||||||
entity_id='',
|
entity_id="",
|
||||||
entity_name='System',
|
entity_name="System",
|
||||||
description=description,
|
description=description,
|
||||||
changes=details
|
changes=details,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def track_model_changes(old_instance, new_instance, exclude_fields=None):
|
def track_model_changes(old_instance, new_instance, exclude_fields=None):
|
||||||
"""
|
"""
|
||||||
Track changes between model instances
|
Track changes between model instances
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
old_instance: Original model instance
|
old_instance: Original model instance
|
||||||
new_instance: Updated model instance
|
new_instance: Updated model instance
|
||||||
exclude_fields: List of fields to exclude from tracking
|
exclude_fields: List of fields to exclude from tracking
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary of changes in format {field: {'old': old_value, 'new': new_value}}
|
Dictionary of changes in format {field: {'old': old_value, 'new': new_value}}
|
||||||
"""
|
"""
|
||||||
if exclude_fields is None:
|
if exclude_fields is None:
|
||||||
exclude_fields = ['id', 'erstellt_am', 'aktualisiert_am', 'created_at', 'updated_at']
|
exclude_fields = [
|
||||||
|
"id",
|
||||||
|
"erstellt_am",
|
||||||
|
"aktualisiert_am",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
]
|
||||||
|
|
||||||
changes = {}
|
changes = {}
|
||||||
|
|
||||||
if old_instance and new_instance:
|
if old_instance and new_instance:
|
||||||
for field in new_instance._meta.fields:
|
for field in new_instance._meta.fields:
|
||||||
field_name = field.name
|
field_name = field.name
|
||||||
|
|
||||||
if field_name in exclude_fields:
|
if field_name in exclude_fields:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
old_value = getattr(old_instance, field_name, None)
|
old_value = getattr(old_instance, field_name, None)
|
||||||
new_value = getattr(new_instance, field_name, None)
|
new_value = getattr(new_instance, field_name, None)
|
||||||
|
|
||||||
# Convert to string for comparison
|
# Convert to string for comparison
|
||||||
old_str = str(old_value) if old_value is not None else None
|
old_str = str(old_value) if old_value is not None else None
|
||||||
new_str = str(new_value) if new_value is not None else None
|
new_str = str(new_value) if new_value is not None else None
|
||||||
|
|
||||||
if old_str != new_str:
|
if old_str != new_str:
|
||||||
changes[field_name] = {
|
changes[field_name] = {"old": old_str, "new": new_str}
|
||||||
'old': old_str,
|
|
||||||
'new': new_str
|
|
||||||
}
|
|
||||||
|
|
||||||
return changes
|
return changes
|
||||||
|
|
||||||
|
|
||||||
@@ -195,38 +220,39 @@ class AuditLogMixin:
|
|||||||
"""
|
"""
|
||||||
Mixin for views that provides audit logging functionality
|
Mixin for views that provides audit logging functionality
|
||||||
"""
|
"""
|
||||||
|
|
||||||
audit_entity_type = None
|
audit_entity_type = None
|
||||||
audit_entity_name_field = 'name'
|
audit_entity_name_field = "name"
|
||||||
|
|
||||||
def get_audit_entity_type(self):
|
def get_audit_entity_type(self):
|
||||||
"""Get the entity type for audit logging"""
|
"""Get the entity type for audit logging"""
|
||||||
if self.audit_entity_type:
|
if self.audit_entity_type:
|
||||||
return self.audit_entity_type
|
return self.audit_entity_type
|
||||||
|
|
||||||
# Try to derive from model name
|
# Try to derive from model name
|
||||||
if hasattr(self, 'model') and self.model:
|
if hasattr(self, "model") and self.model:
|
||||||
return self.model.__name__.lower()
|
return self.model.__name__.lower()
|
||||||
|
|
||||||
return 'unknown'
|
return "unknown"
|
||||||
|
|
||||||
def get_audit_entity_name(self, instance):
|
def get_audit_entity_name(self, instance):
|
||||||
"""Get the entity name for audit logging"""
|
"""Get the entity name for audit logging"""
|
||||||
if hasattr(instance, self.audit_entity_name_field):
|
if hasattr(instance, self.audit_entity_name_field):
|
||||||
return str(getattr(instance, self.audit_entity_name_field))
|
return str(getattr(instance, self.audit_entity_name_field))
|
||||||
elif hasattr(instance, '__str__'):
|
elif hasattr(instance, "__str__"):
|
||||||
return str(instance)
|
return str(instance)
|
||||||
else:
|
else:
|
||||||
return f"{self.get_audit_entity_type()} #{instance.pk}"
|
return f"{self.get_audit_entity_type()} #{instance.pk}"
|
||||||
|
|
||||||
def log_create_action(self, instance):
|
def log_create_action(self, instance):
|
||||||
"""Log creation of an instance"""
|
"""Log creation of an instance"""
|
||||||
log_create(
|
log_create(
|
||||||
request=self.request,
|
request=self.request,
|
||||||
entity_type=self.get_audit_entity_type(),
|
entity_type=self.get_audit_entity_type(),
|
||||||
entity_id=instance.pk,
|
entity_id=instance.pk,
|
||||||
entity_name=self.get_audit_entity_name(instance)
|
entity_name=self.get_audit_entity_name(instance),
|
||||||
)
|
)
|
||||||
|
|
||||||
def log_update_action(self, old_instance, new_instance):
|
def log_update_action(self, old_instance, new_instance):
|
||||||
"""Log update of an instance"""
|
"""Log update of an instance"""
|
||||||
changes = track_model_changes(old_instance, new_instance)
|
changes = track_model_changes(old_instance, new_instance)
|
||||||
@@ -236,16 +262,16 @@ class AuditLogMixin:
|
|||||||
entity_type=self.get_audit_entity_type(),
|
entity_type=self.get_audit_entity_type(),
|
||||||
entity_id=new_instance.pk,
|
entity_id=new_instance.pk,
|
||||||
entity_name=self.get_audit_entity_name(new_instance),
|
entity_name=self.get_audit_entity_name(new_instance),
|
||||||
changes=changes
|
changes=changes,
|
||||||
)
|
)
|
||||||
|
|
||||||
def log_delete_action(self, instance):
|
def log_delete_action(self, instance):
|
||||||
"""Log deletion of an instance"""
|
"""Log deletion of an instance"""
|
||||||
log_delete(
|
log_delete(
|
||||||
request=self.request,
|
request=self.request,
|
||||||
entity_type=self.get_audit_entity_type(),
|
entity_type=self.get_audit_entity_type(),
|
||||||
entity_id=instance.pk,
|
entity_id=instance.pk,
|
||||||
entity_name=self.get_audit_entity_name(instance)
|
entity_name=self.get_audit_entity_name(instance),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -255,11 +281,11 @@ def log_login(request, user):
|
|||||||
try:
|
try:
|
||||||
return log_action(
|
return log_action(
|
||||||
request=request,
|
request=request,
|
||||||
action='login',
|
action="login",
|
||||||
entity_type='user',
|
entity_type="user",
|
||||||
entity_id=user.pk,
|
entity_id=user.pk,
|
||||||
entity_name=user.get_username(),
|
entity_name=user.get_username(),
|
||||||
description=f"User '{user.get_username()}' logged in"
|
description=f"User '{user.get_username()}' logged in",
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
@@ -268,14 +294,14 @@ def log_login(request, user):
|
|||||||
def log_logout(request, user):
|
def log_logout(request, user):
|
||||||
"""Log a successful user logout."""
|
"""Log a successful user logout."""
|
||||||
try:
|
try:
|
||||||
username = user.get_username() if user else 'Unknown'
|
username = user.get_username() if user else "Unknown"
|
||||||
return log_action(
|
return log_action(
|
||||||
request=request,
|
request=request,
|
||||||
action='logout',
|
action="logout",
|
||||||
entity_type='user',
|
entity_type="user",
|
||||||
entity_id=getattr(user, 'pk', ''),
|
entity_id=getattr(user, "pk", ""),
|
||||||
entity_name=username,
|
entity_name=username,
|
||||||
description=f"User '{username}' logged out"
|
description=f"User '{username}' logged out",
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -6,17 +6,19 @@ Handles creation and restoration of complete system backups
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
|
||||||
import tarfile
|
import tarfile
|
||||||
|
import tempfile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from stiftung.models import BackupJob
|
from stiftung.models import BackupJob
|
||||||
|
|
||||||
|
|
||||||
def get_backup_directory():
|
def get_backup_directory():
|
||||||
"""Get or create the backup directory"""
|
"""Get or create the backup directory"""
|
||||||
backup_dir = '/app/backups'
|
backup_dir = "/app/backups"
|
||||||
os.makedirs(backup_dir, exist_ok=True)
|
os.makedirs(backup_dir, exist_ok=True)
|
||||||
return backup_dir
|
return backup_dir
|
||||||
|
|
||||||
@@ -28,48 +30,48 @@ def run_backup(backup_job_id):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
backup_job = BackupJob.objects.get(id=backup_job_id)
|
backup_job = BackupJob.objects.get(id=backup_job_id)
|
||||||
backup_job.status = 'running'
|
backup_job.status = "running"
|
||||||
backup_job.started_at = timezone.now()
|
backup_job.started_at = timezone.now()
|
||||||
backup_job.save()
|
backup_job.save()
|
||||||
|
|
||||||
backup_dir = get_backup_directory()
|
backup_dir = get_backup_directory()
|
||||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
backup_filename = f"stiftung_backup_{timestamp}.tar.gz"
|
backup_filename = f"stiftung_backup_{timestamp}.tar.gz"
|
||||||
backup_path = os.path.join(backup_dir, backup_filename)
|
backup_path = os.path.join(backup_dir, backup_filename)
|
||||||
|
|
||||||
# Create temporary directory for backup staging
|
# Create temporary directory for backup staging
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
staging_dir = os.path.join(temp_dir, 'backup_staging')
|
staging_dir = os.path.join(temp_dir, "backup_staging")
|
||||||
os.makedirs(staging_dir)
|
os.makedirs(staging_dir)
|
||||||
|
|
||||||
# 1. Database backup
|
# 1. Database backup
|
||||||
if backup_job.backup_type in ['full', 'database']:
|
if backup_job.backup_type in ["full", "database"]:
|
||||||
db_backup_path = create_database_backup(staging_dir)
|
db_backup_path = create_database_backup(staging_dir)
|
||||||
if not db_backup_path:
|
if not db_backup_path:
|
||||||
raise Exception("Database backup failed")
|
raise Exception("Database backup failed")
|
||||||
|
|
||||||
# 2. Files backup
|
# 2. Files backup
|
||||||
if backup_job.backup_type in ['full', 'files']:
|
if backup_job.backup_type in ["full", "files"]:
|
||||||
files_backup_path = create_files_backup(staging_dir)
|
files_backup_path = create_files_backup(staging_dir)
|
||||||
if not files_backup_path:
|
if not files_backup_path:
|
||||||
raise Exception("Files backup failed")
|
raise Exception("Files backup failed")
|
||||||
|
|
||||||
# 3. Create metadata file
|
# 3. Create metadata file
|
||||||
create_backup_metadata(staging_dir, backup_job)
|
create_backup_metadata(staging_dir, backup_job)
|
||||||
|
|
||||||
# 4. Create compressed archive
|
# 4. Create compressed archive
|
||||||
create_compressed_backup(staging_dir, backup_path)
|
create_compressed_backup(staging_dir, backup_path)
|
||||||
|
|
||||||
# 5. Update job status
|
# 5. Update job status
|
||||||
backup_size = os.path.getsize(backup_path)
|
backup_size = os.path.getsize(backup_path)
|
||||||
backup_job.status = 'completed'
|
backup_job.status = "completed"
|
||||||
backup_job.completed_at = timezone.now()
|
backup_job.completed_at = timezone.now()
|
||||||
backup_job.backup_filename = backup_filename
|
backup_job.backup_filename = backup_filename
|
||||||
backup_job.backup_size = backup_size
|
backup_job.backup_size = backup_size
|
||||||
backup_job.save()
|
backup_job.save()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
backup_job.status = 'failed'
|
backup_job.status = "failed"
|
||||||
backup_job.error_message = str(e)
|
backup_job.error_message = str(e)
|
||||||
backup_job.completed_at = timezone.now()
|
backup_job.completed_at = timezone.now()
|
||||||
backup_job.save()
|
backup_job.save()
|
||||||
@@ -78,37 +80,42 @@ def run_backup(backup_job_id):
|
|||||||
def create_database_backup(staging_dir):
|
def create_database_backup(staging_dir):
|
||||||
"""Create a database backup using pg_dump"""
|
"""Create a database backup using pg_dump"""
|
||||||
try:
|
try:
|
||||||
db_backup_file = os.path.join(staging_dir, 'database.sql')
|
db_backup_file = os.path.join(staging_dir, "database.sql")
|
||||||
|
|
||||||
# Get database settings
|
# Get database settings
|
||||||
db_settings = settings.DATABASES['default']
|
db_settings = settings.DATABASES["default"]
|
||||||
|
|
||||||
# Build pg_dump command
|
# Build pg_dump command
|
||||||
cmd = [
|
cmd = [
|
||||||
'pg_dump',
|
"pg_dump",
|
||||||
'--host', db_settings.get('HOST', 'localhost'),
|
"--host",
|
||||||
'--port', str(db_settings.get('PORT', 5432)),
|
db_settings.get("HOST", "localhost"),
|
||||||
'--username', db_settings.get('USER', 'postgres'),
|
"--port",
|
||||||
'--format', 'custom',
|
str(db_settings.get("PORT", 5432)),
|
||||||
'--no-owner', # portability across environments
|
"--username",
|
||||||
'--no-privileges', # skip GRANT/REVOKE
|
db_settings.get("USER", "postgres"),
|
||||||
'--no-password',
|
"--format",
|
||||||
'--file', db_backup_file,
|
"custom",
|
||||||
db_settings.get('NAME', 'stiftung')
|
"--no-owner", # portability across environments
|
||||||
|
"--no-privileges", # skip GRANT/REVOKE
|
||||||
|
"--no-password",
|
||||||
|
"--file",
|
||||||
|
db_backup_file,
|
||||||
|
db_settings.get("NAME", "stiftung"),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Set environment variables for authentication
|
# Set environment variables for authentication
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env['PGPASSWORD'] = db_settings.get('PASSWORD', '')
|
env["PGPASSWORD"] = db_settings.get("PASSWORD", "")
|
||||||
|
|
||||||
# Run pg_dump
|
# Run pg_dump
|
||||||
result = subprocess.run(cmd, env=env, capture_output=True, text=True)
|
result = subprocess.run(cmd, env=env, capture_output=True, text=True)
|
||||||
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
raise Exception(f"pg_dump failed: {result.stderr}")
|
raise Exception(f"pg_dump failed: {result.stderr}")
|
||||||
|
|
||||||
return db_backup_file
|
return db_backup_file
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Database backup failed: {e}")
|
print(f"Database backup failed: {e}")
|
||||||
return None
|
return None
|
||||||
@@ -117,28 +124,28 @@ def create_database_backup(staging_dir):
|
|||||||
def create_files_backup(staging_dir):
|
def create_files_backup(staging_dir):
|
||||||
"""Create backup of application files"""
|
"""Create backup of application files"""
|
||||||
try:
|
try:
|
||||||
files_dir = os.path.join(staging_dir, 'files')
|
files_dir = os.path.join(staging_dir, "files")
|
||||||
os.makedirs(files_dir)
|
os.makedirs(files_dir)
|
||||||
|
|
||||||
# Files to backup
|
# Files to backup
|
||||||
backup_paths = [
|
backup_paths = [
|
||||||
'/app/media', # User uploads
|
"/app/media", # User uploads
|
||||||
'/app/static', # Static files
|
"/app/static", # Static files
|
||||||
'/app/.env', # Environment configuration
|
"/app/.env", # Environment configuration
|
||||||
]
|
]
|
||||||
|
|
||||||
for source_path in backup_paths:
|
for source_path in backup_paths:
|
||||||
if os.path.exists(source_path):
|
if os.path.exists(source_path):
|
||||||
basename = os.path.basename(source_path)
|
basename = os.path.basename(source_path)
|
||||||
dest_path = os.path.join(files_dir, basename)
|
dest_path = os.path.join(files_dir, basename)
|
||||||
|
|
||||||
if os.path.isdir(source_path):
|
if os.path.isdir(source_path):
|
||||||
shutil.copytree(source_path, dest_path)
|
shutil.copytree(source_path, dest_path)
|
||||||
else:
|
else:
|
||||||
shutil.copy2(source_path, dest_path)
|
shutil.copy2(source_path, dest_path)
|
||||||
|
|
||||||
return files_dir
|
return files_dir
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Files backup failed: {e}")
|
print(f"Files backup failed: {e}")
|
||||||
return None
|
return None
|
||||||
@@ -147,26 +154,28 @@ def create_files_backup(staging_dir):
|
|||||||
def create_backup_metadata(staging_dir, backup_job):
|
def create_backup_metadata(staging_dir, backup_job):
|
||||||
"""Create metadata file with backup information"""
|
"""Create metadata file with backup information"""
|
||||||
import json
|
import json
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
'backup_id': str(backup_job.id),
|
"backup_id": str(backup_job.id),
|
||||||
'backup_type': backup_job.backup_type,
|
"backup_type": backup_job.backup_type,
|
||||||
'created_at': backup_job.created_at.isoformat(),
|
"created_at": backup_job.created_at.isoformat(),
|
||||||
'created_by': backup_job.created_by.username if backup_job.created_by else 'system',
|
"created_by": (
|
||||||
'django_version': '5.0.6',
|
backup_job.created_by.username if backup_job.created_by else "system"
|
||||||
'app_version': '1.0.0',
|
),
|
||||||
'python_version': '3.12',
|
"django_version": "5.0.6",
|
||||||
|
"app_version": "1.0.0",
|
||||||
|
"python_version": "3.12",
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata_file = os.path.join(staging_dir, 'backup_metadata.json')
|
metadata_file = os.path.join(staging_dir, "backup_metadata.json")
|
||||||
with open(metadata_file, 'w') as f:
|
with open(metadata_file, "w") as f:
|
||||||
json.dump(metadata, f, indent=2)
|
json.dump(metadata, f, indent=2)
|
||||||
|
|
||||||
|
|
||||||
def create_compressed_backup(staging_dir, backup_path):
|
def create_compressed_backup(staging_dir, backup_path):
|
||||||
"""Create compressed tar.gz archive"""
|
"""Create compressed tar.gz archive"""
|
||||||
with tarfile.open(backup_path, 'w:gz') as tar:
|
with tarfile.open(backup_path, "w:gz") as tar:
|
||||||
tar.add(staging_dir, arcname='.')
|
tar.add(staging_dir, arcname=".")
|
||||||
|
|
||||||
|
|
||||||
def run_restore(restore_job_id, backup_file_path):
|
def run_restore(restore_job_id, backup_file_path):
|
||||||
@@ -176,46 +185,47 @@ def run_restore(restore_job_id, backup_file_path):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
restore_job = BackupJob.objects.get(id=restore_job_id)
|
restore_job = BackupJob.objects.get(id=restore_job_id)
|
||||||
restore_job.status = 'running'
|
restore_job.status = "running"
|
||||||
restore_job.started_at = timezone.now()
|
restore_job.started_at = timezone.now()
|
||||||
restore_job.save()
|
restore_job.save()
|
||||||
|
|
||||||
# Extract backup
|
# Extract backup
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
extract_dir = os.path.join(temp_dir, 'restore')
|
extract_dir = os.path.join(temp_dir, "restore")
|
||||||
os.makedirs(extract_dir)
|
os.makedirs(extract_dir)
|
||||||
|
|
||||||
# Extract tar.gz
|
# Extract tar.gz
|
||||||
with tarfile.open(backup_file_path, 'r:gz') as tar:
|
with tarfile.open(backup_file_path, "r:gz") as tar:
|
||||||
tar.extractall(extract_dir)
|
tar.extractall(extract_dir)
|
||||||
|
|
||||||
# Validate backup
|
# Validate backup
|
||||||
metadata_file = os.path.join(extract_dir, 'backup_metadata.json')
|
metadata_file = os.path.join(extract_dir, "backup_metadata.json")
|
||||||
if not os.path.exists(metadata_file):
|
if not os.path.exists(metadata_file):
|
||||||
raise Exception("Invalid backup: missing metadata")
|
raise Exception("Invalid backup: missing metadata")
|
||||||
|
|
||||||
# Read metadata
|
# Read metadata
|
||||||
import json
|
import json
|
||||||
with open(metadata_file, 'r') as f:
|
|
||||||
|
with open(metadata_file, "r") as f:
|
||||||
metadata = json.load(f)
|
metadata = json.load(f)
|
||||||
|
|
||||||
# Restore database
|
# Restore database
|
||||||
db_backup_file = os.path.join(extract_dir, 'database.sql')
|
db_backup_file = os.path.join(extract_dir, "database.sql")
|
||||||
if os.path.exists(db_backup_file):
|
if os.path.exists(db_backup_file):
|
||||||
restore_database(db_backup_file)
|
restore_database(db_backup_file)
|
||||||
|
|
||||||
# Restore files
|
# Restore files
|
||||||
files_dir = os.path.join(extract_dir, 'files')
|
files_dir = os.path.join(extract_dir, "files")
|
||||||
if os.path.exists(files_dir):
|
if os.path.exists(files_dir):
|
||||||
restore_files(files_dir)
|
restore_files(files_dir)
|
||||||
|
|
||||||
# Update job status
|
# Update job status
|
||||||
restore_job.status = 'completed'
|
restore_job.status = "completed"
|
||||||
restore_job.completed_at = timezone.now()
|
restore_job.completed_at = timezone.now()
|
||||||
restore_job.save()
|
restore_job.save()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
restore_job.status = 'failed'
|
restore_job.status = "failed"
|
||||||
restore_job.error_message = str(e)
|
restore_job.error_message = str(e)
|
||||||
restore_job.completed_at = timezone.now()
|
restore_job.completed_at = timezone.now()
|
||||||
restore_job.save()
|
restore_job.save()
|
||||||
@@ -225,42 +235,47 @@ def restore_database(db_backup_file):
|
|||||||
"""Restore database from backup"""
|
"""Restore database from backup"""
|
||||||
try:
|
try:
|
||||||
# Get database settings
|
# Get database settings
|
||||||
db_settings = settings.DATABASES['default']
|
db_settings = settings.DATABASES["default"]
|
||||||
|
|
||||||
# Build pg_restore command
|
# Build pg_restore command
|
||||||
cmd = [
|
cmd = [
|
||||||
'pg_restore',
|
"pg_restore",
|
||||||
'--host', db_settings.get('HOST', 'localhost'),
|
"--host",
|
||||||
'--port', str(db_settings.get('PORT', 5432)),
|
db_settings.get("HOST", "localhost"),
|
||||||
'--username', db_settings.get('USER', 'postgres'),
|
"--port",
|
||||||
'--dbname', db_settings.get('NAME', 'stiftung'),
|
str(db_settings.get("PORT", 5432)),
|
||||||
'--clean', # Drop existing objects first
|
"--username",
|
||||||
'--if-exists', # Don't error if objects don't exist
|
db_settings.get("USER", "postgres"),
|
||||||
'--no-owner', # don't attempt to set original owners
|
"--dbname",
|
||||||
'--role', db_settings.get('USER', 'postgres'), # set target owner
|
db_settings.get("NAME", "stiftung"),
|
||||||
'--single-transaction', # restore atomically when possible
|
"--clean", # Drop existing objects first
|
||||||
'--disable-triggers', # avoid FK issues during data load
|
"--if-exists", # Don't error if objects don't exist
|
||||||
'--no-password',
|
"--no-owner", # don't attempt to set original owners
|
||||||
'--verbose',
|
"--role",
|
||||||
db_backup_file
|
db_settings.get("USER", "postgres"), # set target owner
|
||||||
|
"--single-transaction", # restore atomically when possible
|
||||||
|
"--disable-triggers", # avoid FK issues during data load
|
||||||
|
"--no-password",
|
||||||
|
"--verbose",
|
||||||
|
db_backup_file,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Set environment variables for authentication
|
# Set environment variables for authentication
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env['PGPASSWORD'] = db_settings.get('PASSWORD', '')
|
env["PGPASSWORD"] = db_settings.get("PASSWORD", "")
|
||||||
|
|
||||||
# Run pg_restore
|
# Run pg_restore
|
||||||
result = subprocess.run(cmd, env=env, capture_output=True, text=True)
|
result = subprocess.run(cmd, env=env, capture_output=True, text=True)
|
||||||
|
|
||||||
# Fail if there are real errors
|
# Fail if there are real errors
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
stderr = result.stderr or ''
|
stderr = result.stderr or ""
|
||||||
# escalate only if we see ERROR
|
# escalate only if we see ERROR
|
||||||
if 'ERROR' in stderr.upper():
|
if "ERROR" in stderr.upper():
|
||||||
raise Exception(f"pg_restore failed: {stderr}")
|
raise Exception(f"pg_restore failed: {stderr}")
|
||||||
else:
|
else:
|
||||||
print(f"pg_restore completed with warnings: {stderr}")
|
print(f"pg_restore completed with warnings: {stderr}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Database restore failed: {e}")
|
raise Exception(f"Database restore failed: {e}")
|
||||||
|
|
||||||
@@ -270,29 +285,31 @@ def restore_files(files_dir):
|
|||||||
try:
|
try:
|
||||||
# Restore paths
|
# Restore paths
|
||||||
restore_mappings = {
|
restore_mappings = {
|
||||||
'media': '/app/media',
|
"media": "/app/media",
|
||||||
'static': '/app/static',
|
"static": "/app/static",
|
||||||
'.env': '/app/.env',
|
".env": "/app/.env",
|
||||||
}
|
}
|
||||||
|
|
||||||
for source_name, dest_path in restore_mappings.items():
|
for source_name, dest_path in restore_mappings.items():
|
||||||
source_path = os.path.join(files_dir, source_name)
|
source_path = os.path.join(files_dir, source_name)
|
||||||
|
|
||||||
if os.path.exists(source_path):
|
if os.path.exists(source_path):
|
||||||
# Backup existing files first
|
# Backup existing files first
|
||||||
if os.path.exists(dest_path):
|
if os.path.exists(dest_path):
|
||||||
backup_path = f"{dest_path}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
backup_path = (
|
||||||
|
f"{dest_path}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||||
|
)
|
||||||
if os.path.isdir(dest_path):
|
if os.path.isdir(dest_path):
|
||||||
shutil.move(dest_path, backup_path)
|
shutil.move(dest_path, backup_path)
|
||||||
else:
|
else:
|
||||||
shutil.copy2(dest_path, backup_path)
|
shutil.copy2(dest_path, backup_path)
|
||||||
|
|
||||||
# Restore files
|
# Restore files
|
||||||
if os.path.isdir(source_path):
|
if os.path.isdir(source_path):
|
||||||
shutil.copytree(source_path, dest_path)
|
shutil.copytree(source_path, dest_path)
|
||||||
else:
|
else:
|
||||||
shutil.copy2(source_path, dest_path)
|
shutil.copy2(source_path, dest_path)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Files restore failed: {e}")
|
raise Exception(f"Files restore failed: {e}")
|
||||||
|
|
||||||
@@ -302,19 +319,19 @@ def cleanup_old_backups(keep_count=10):
|
|||||||
try:
|
try:
|
||||||
backup_dir = get_backup_directory()
|
backup_dir = get_backup_directory()
|
||||||
backup_files = []
|
backup_files = []
|
||||||
|
|
||||||
for filename in os.listdir(backup_dir):
|
for filename in os.listdir(backup_dir):
|
||||||
if filename.startswith('stiftung_backup_') and filename.endswith('.tar.gz'):
|
if filename.startswith("stiftung_backup_") and filename.endswith(".tar.gz"):
|
||||||
filepath = os.path.join(backup_dir, filename)
|
filepath = os.path.join(backup_dir, filename)
|
||||||
backup_files.append((filepath, os.path.getmtime(filepath)))
|
backup_files.append((filepath, os.path.getmtime(filepath)))
|
||||||
|
|
||||||
# Sort by modification time (newest first)
|
# Sort by modification time (newest first)
|
||||||
backup_files.sort(key=lambda x: x[1], reverse=True)
|
backup_files.sort(key=lambda x: x[1], reverse=True)
|
||||||
|
|
||||||
# Remove old backups
|
# Remove old backups
|
||||||
for filepath, _ in backup_files[keep_count:]:
|
for filepath, _ in backup_files[keep_count:]:
|
||||||
os.remove(filepath)
|
os.remove(filepath)
|
||||||
print(f"Removed old backup: {os.path.basename(filepath)}")
|
print(f"Removed old backup: {os.path.basename(filepath)}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Cleanup failed: {e}")
|
print(f"Cleanup failed: {e}")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,60 +3,64 @@ Management command to generate due recurring support payments.
|
|||||||
This command should be run daily via cron or similar scheduling system.
|
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.core.management.base import BaseCommand
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from datetime import timedelta
|
|
||||||
from stiftung.models import UnterstuetzungWiederkehrend
|
from stiftung.models import UnterstuetzungWiederkehrend
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Generate due recurring support payments'
|
help = "Generate due recurring support payments"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--dry-run',
|
"--dry-run",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Show what would be generated without actually creating payments',
|
help="Show what would be generated without actually creating payments",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--days-ahead',
|
"--days-ahead",
|
||||||
type=int,
|
type=int,
|
||||||
default=0,
|
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):
|
def handle(self, *args, **options):
|
||||||
dry_run = options['dry_run']
|
dry_run = options["dry_run"]
|
||||||
days_ahead = options['days_ahead']
|
days_ahead = options["days_ahead"]
|
||||||
|
|
||||||
heute = timezone.now().date()
|
heute = timezone.now().date()
|
||||||
cutoff_date = heute + timedelta(days=days_ahead)
|
cutoff_date = heute + timedelta(days=days_ahead)
|
||||||
|
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(
|
self.style.SUCCESS(
|
||||||
f'Checking for recurring payments due up to {cutoff_date.strftime("%d.%m.%Y")}...'
|
f'Checking for recurring payments due up to {cutoff_date.strftime("%d.%m.%Y")}...'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if dry_run:
|
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
|
# Get all active recurring payment templates that are due
|
||||||
templates = UnterstuetzungWiederkehrend.objects.filter(
|
templates = UnterstuetzungWiederkehrend.objects.filter(
|
||||||
aktiv=True,
|
aktiv=True, naechste_generierung__lte=cutoff_date
|
||||||
naechste_generierung__lte=cutoff_date
|
).select_related("destinataer", "konto")
|
||||||
).select_related('destinataer', 'konto')
|
|
||||||
|
|
||||||
generated_count = 0
|
generated_count = 0
|
||||||
error_count = 0
|
error_count = 0
|
||||||
|
|
||||||
for template in templates:
|
for template in templates:
|
||||||
try:
|
try:
|
||||||
if dry_run:
|
if dry_run:
|
||||||
self.stdout.write(
|
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")}'
|
f'€{template.betrag} due {template.naechste_generierung.strftime("%d.%m.%Y")}'
|
||||||
)
|
)
|
||||||
generated_count += 1
|
generated_count += 1
|
||||||
@@ -66,68 +70,67 @@ class Command(BaseCommand):
|
|||||||
if neue_zahlung:
|
if neue_zahlung:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(
|
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")}'
|
f'€{neue_zahlung.betrag} due {neue_zahlung.faellig_am.strftime("%d.%m.%Y")}'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
generated_count += 1
|
generated_count += 1
|
||||||
logger.info(f'Generated recurring payment: {neue_zahlung.pk}')
|
logger.info(f"Generated recurring payment: {neue_zahlung.pk}")
|
||||||
else:
|
else:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.WARNING(
|
self.style.WARNING(
|
||||||
f'No payment generated for {template.destinataer.get_full_name()} '
|
f"No payment generated for {template.destinataer.get_full_name()} "
|
||||||
f'(may have reached end date or not yet due)'
|
f"(may have reached end date or not yet due)"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_count += 1
|
error_count += 1
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.ERROR(
|
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
|
# Summary
|
||||||
self.stdout.write('\n' + '='*50)
|
self.stdout.write("\n" + "=" * 50)
|
||||||
if dry_run:
|
if dry_run:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(
|
self.style.SUCCESS(
|
||||||
f'DRY RUN COMPLETE: {generated_count} payments would be generated'
|
f"DRY RUN COMPLETE: {generated_count} payments would be generated"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(
|
self.style.SUCCESS(
|
||||||
f'GENERATION COMPLETE: {generated_count} payments generated'
|
f"GENERATION COMPLETE: {generated_count} payments generated"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if error_count > 0:
|
if error_count > 0:
|
||||||
self.stdout.write(
|
self.stdout.write(self.style.ERROR(f"{error_count} errors encountered"))
|
||||||
self.style.ERROR(f'{error_count} errors encountered')
|
|
||||||
)
|
|
||||||
|
|
||||||
# Also check for overdue payments and report them
|
# Also check for overdue payments and report them
|
||||||
from stiftung.models import DestinataerUnterstuetzung
|
from stiftung.models import DestinataerUnterstuetzung
|
||||||
|
|
||||||
overdue_payments = DestinataerUnterstuetzung.objects.filter(
|
overdue_payments = DestinataerUnterstuetzung.objects.filter(
|
||||||
faellig_am__lt=heute,
|
faellig_am__lt=heute, status__in=["geplant", "faellig"]
|
||||||
status__in=['geplant', 'faellig']
|
).select_related("destinataer")
|
||||||
).select_related('destinataer')
|
|
||||||
|
|
||||||
if overdue_payments.exists():
|
if overdue_payments.exists():
|
||||||
self.stdout.write('\n' + '='*50)
|
self.stdout.write("\n" + "=" * 50)
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.WARNING(
|
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
|
for payment in overdue_payments[:10]: # Limit to first 10
|
||||||
days_overdue = (heute - payment.faellig_am).days
|
days_overdue = (heute - payment.faellig_am).days
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
f' - {payment.destinataer.get_full_name()}: €{payment.betrag} '
|
f" - {payment.destinataer.get_full_name()}: €{payment.betrag} "
|
||||||
f'({days_overdue} days overdue)'
|
f"({days_overdue} days overdue)"
|
||||||
)
|
)
|
||||||
if overdue_payments.count() > 10:
|
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 django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from stiftung.models import AppConfiguration
|
from stiftung.models import AppConfiguration
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Initialize default app configuration settings'
|
help = "Initialize default app configuration settings"
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
# Paperless Integration Settings
|
# Paperless Integration Settings
|
||||||
paperless_settings = [
|
paperless_settings = [
|
||||||
{
|
{
|
||||||
'key': 'paperless_api_url',
|
"key": "paperless_api_url",
|
||||||
'display_name': 'Paperless API URL',
|
"display_name": "Paperless API URL",
|
||||||
'description': 'The base URL for your Paperless-NGX API (e.g., http://paperless.example.com:8000)',
|
"description": "The base URL for your Paperless-NGX API (e.g., http://paperless.example.com:8000)",
|
||||||
'value': 'http://192.168.178.167:30070',
|
"value": "http://192.168.178.167:30070",
|
||||||
'default_value': 'http://192.168.178.167:30070',
|
"default_value": "http://192.168.178.167:30070",
|
||||||
'setting_type': 'url',
|
"setting_type": "url",
|
||||||
'category': 'paperless',
|
"category": "paperless",
|
||||||
'order': 1
|
"order": 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'paperless_api_token',
|
"key": "paperless_api_token",
|
||||||
'display_name': 'Paperless API Token',
|
"display_name": "Paperless API Token",
|
||||||
'description': 'The authentication token for Paperless API access',
|
"description": "The authentication token for Paperless API access",
|
||||||
'value': '',
|
"value": "",
|
||||||
'default_value': '',
|
"default_value": "",
|
||||||
'setting_type': 'text',
|
"setting_type": "text",
|
||||||
'category': 'paperless',
|
"category": "paperless",
|
||||||
'order': 2
|
"order": 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'paperless_destinataere_tag',
|
"key": "paperless_destinataere_tag",
|
||||||
'display_name': 'Destinatäre Tag Name',
|
"display_name": "Destinatäre Tag Name",
|
||||||
'description': 'The tag name used to identify Destinatäre documents in Paperless',
|
"description": "The tag name used to identify Destinatäre documents in Paperless",
|
||||||
'value': 'Stiftung_Destinatäre',
|
"value": "Stiftung_Destinatäre",
|
||||||
'default_value': 'Stiftung_Destinatäre',
|
"default_value": "Stiftung_Destinatäre",
|
||||||
'setting_type': 'tag',
|
"setting_type": "tag",
|
||||||
'category': 'paperless',
|
"category": "paperless",
|
||||||
'order': 3
|
"order": 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'paperless_destinataere_tag_id',
|
"key": "paperless_destinataere_tag_id",
|
||||||
'display_name': 'Destinatäre Tag ID',
|
"display_name": "Destinatäre Tag ID",
|
||||||
'description': 'The numeric ID of the Destinatäre tag in Paperless',
|
"description": "The numeric ID of the Destinatäre tag in Paperless",
|
||||||
'value': '210',
|
"value": "210",
|
||||||
'default_value': '210',
|
"default_value": "210",
|
||||||
'setting_type': 'tag_id',
|
"setting_type": "tag_id",
|
||||||
'category': 'paperless',
|
"category": "paperless",
|
||||||
'order': 4
|
"order": 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'paperless_land_tag',
|
"key": "paperless_land_tag",
|
||||||
'display_name': 'Land & Pächter Tag Name',
|
"display_name": "Land & Pächter Tag Name",
|
||||||
'description': 'The tag name used to identify Land and Pächter documents in Paperless',
|
"description": "The tag name used to identify Land and Pächter documents in Paperless",
|
||||||
'value': 'Stiftung_Land_und_Pächter',
|
"value": "Stiftung_Land_und_Pächter",
|
||||||
'default_value': 'Stiftung_Land_und_Pächter',
|
"default_value": "Stiftung_Land_und_Pächter",
|
||||||
'setting_type': 'tag',
|
"setting_type": "tag",
|
||||||
'category': 'paperless',
|
"category": "paperless",
|
||||||
'order': 5
|
"order": 5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'paperless_land_tag_id',
|
"key": "paperless_land_tag_id",
|
||||||
'display_name': 'Land & Pächter Tag ID',
|
"display_name": "Land & Pächter Tag ID",
|
||||||
'description': 'The numeric ID of the Land & Pächter tag in Paperless',
|
"description": "The numeric ID of the Land & Pächter tag in Paperless",
|
||||||
'value': '204',
|
"value": "204",
|
||||||
'default_value': '204',
|
"default_value": "204",
|
||||||
'setting_type': 'tag_id',
|
"setting_type": "tag_id",
|
||||||
'category': 'paperless',
|
"category": "paperless",
|
||||||
'order': 6
|
"order": 6,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'paperless_admin_tag',
|
"key": "paperless_admin_tag",
|
||||||
'display_name': 'Administration Tag Name',
|
"display_name": "Administration Tag Name",
|
||||||
'description': 'The tag name used to identify Administration documents in Paperless',
|
"description": "The tag name used to identify Administration documents in Paperless",
|
||||||
'value': 'Stiftung_Administration',
|
"value": "Stiftung_Administration",
|
||||||
'default_value': 'Stiftung_Administration',
|
"default_value": "Stiftung_Administration",
|
||||||
'setting_type': 'tag',
|
"setting_type": "tag",
|
||||||
'category': 'paperless',
|
"category": "paperless",
|
||||||
'order': 7
|
"order": 7,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'paperless_admin_tag_id',
|
"key": "paperless_admin_tag_id",
|
||||||
'display_name': 'Administration Tag ID',
|
"display_name": "Administration Tag ID",
|
||||||
'description': 'The numeric ID of the Administration tag in Paperless',
|
"description": "The numeric ID of the Administration tag in Paperless",
|
||||||
'value': '216',
|
"value": "216",
|
||||||
'default_value': '216',
|
"default_value": "216",
|
||||||
'setting_type': 'tag_id',
|
"setting_type": "tag_id",
|
||||||
'category': 'paperless',
|
"category": "paperless",
|
||||||
'order': 8
|
"order": 8,
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
created_count = 0
|
created_count = 0
|
||||||
@@ -95,26 +96,25 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
for setting_data in paperless_settings:
|
for setting_data in paperless_settings:
|
||||||
setting, created = AppConfiguration.objects.get_or_create(
|
setting, created = AppConfiguration.objects.get_or_create(
|
||||||
key=setting_data['key'],
|
key=setting_data["key"], defaults=setting_data
|
||||||
defaults=setting_data
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
created_count += 1
|
created_count += 1
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(f'Created setting: {setting.display_name}')
|
self.style.SUCCESS(f"Created setting: {setting.display_name}")
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Update existing setting with new defaults if needed
|
# Update existing setting with new defaults if needed
|
||||||
if not setting.description:
|
if not setting.description:
|
||||||
setting.description = setting_data['description']
|
setting.description = setting_data["description"]
|
||||||
setting.save()
|
setting.save()
|
||||||
updated_count += 1
|
updated_count += 1
|
||||||
|
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(
|
self.style.SUCCESS(
|
||||||
f'Configuration initialized successfully! '
|
f"Configuration initialized successfully! "
|
||||||
f'Created {created_count} new settings, updated {updated_count} existing settings.'
|
f"Created {created_count} new settings, updated {updated_count} existing settings."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
|
|||||||
@@ -1,114 +1,116 @@
|
|||||||
"""
|
"""
|
||||||
Management command to initialize corporate identity settings
|
Management command to initialize corporate identity settings
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from stiftung.models import AppConfiguration
|
from stiftung.models import AppConfiguration
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Initialize corporate identity settings for PDF generation'
|
help = "Initialize corporate identity settings for PDF generation"
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
corporate_settings = [
|
corporate_settings = [
|
||||||
{
|
{
|
||||||
'key': 'corporate_stiftung_name',
|
"key": "corporate_stiftung_name",
|
||||||
'display_name': 'Name der Stiftung',
|
"display_name": "Name der Stiftung",
|
||||||
'description': 'Der offizielle Name der Stiftung für PDF-Dokumente',
|
"description": "Der offizielle Name der Stiftung für PDF-Dokumente",
|
||||||
'value': 'Stiftung',
|
"value": "Stiftung",
|
||||||
'default_value': 'Stiftung',
|
"default_value": "Stiftung",
|
||||||
'setting_type': 'text',
|
"setting_type": "text",
|
||||||
'category': 'corporate',
|
"category": "corporate",
|
||||||
'order': 1
|
"order": 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'corporate_logo_path',
|
"key": "corporate_logo_path",
|
||||||
'display_name': 'Logo-Pfad',
|
"display_name": "Logo-Pfad",
|
||||||
'description': 'Pfad zur Logo-Datei (relativ zu MEDIA_ROOT oder STATIC_ROOT)',
|
"description": "Pfad zur Logo-Datei (relativ zu MEDIA_ROOT oder STATIC_ROOT)",
|
||||||
'value': '',
|
"value": "",
|
||||||
'default_value': '',
|
"default_value": "",
|
||||||
'setting_type': 'text',
|
"setting_type": "text",
|
||||||
'category': 'corporate',
|
"category": "corporate",
|
||||||
'order': 2
|
"order": 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'corporate_primary_color',
|
"key": "corporate_primary_color",
|
||||||
'display_name': 'Primärfarbe',
|
"display_name": "Primärfarbe",
|
||||||
'description': 'Hauptfarbe für Überschriften und Akzente (Hex-Code)',
|
"description": "Hauptfarbe für Überschriften und Akzente (Hex-Code)",
|
||||||
'value': '#2c3e50',
|
"value": "#2c3e50",
|
||||||
'default_value': '#2c3e50',
|
"default_value": "#2c3e50",
|
||||||
'setting_type': 'text',
|
"setting_type": "text",
|
||||||
'category': 'corporate',
|
"category": "corporate",
|
||||||
'order': 3
|
"order": 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'corporate_secondary_color',
|
"key": "corporate_secondary_color",
|
||||||
'display_name': 'Sekundärfarbe',
|
"display_name": "Sekundärfarbe",
|
||||||
'description': 'Zweitfarbe für Akzente und Details (Hex-Code)',
|
"description": "Zweitfarbe für Akzente und Details (Hex-Code)",
|
||||||
'value': '#3498db',
|
"value": "#3498db",
|
||||||
'default_value': '#3498db',
|
"default_value": "#3498db",
|
||||||
'setting_type': 'text',
|
"setting_type": "text",
|
||||||
'category': 'corporate',
|
"category": "corporate",
|
||||||
'order': 4
|
"order": 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'corporate_address_line1',
|
"key": "corporate_address_line1",
|
||||||
'display_name': 'Adresse Zeile 1',
|
"display_name": "Adresse Zeile 1",
|
||||||
'description': 'Erste Zeile der Stiftungsadresse',
|
"description": "Erste Zeile der Stiftungsadresse",
|
||||||
'value': '',
|
"value": "",
|
||||||
'default_value': '',
|
"default_value": "",
|
||||||
'setting_type': 'text',
|
"setting_type": "text",
|
||||||
'category': 'corporate',
|
"category": "corporate",
|
||||||
'order': 5
|
"order": 5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'corporate_address_line2',
|
"key": "corporate_address_line2",
|
||||||
'display_name': 'Adresse Zeile 2',
|
"display_name": "Adresse Zeile 2",
|
||||||
'description': 'Zweite Zeile der Stiftungsadresse (PLZ, Ort)',
|
"description": "Zweite Zeile der Stiftungsadresse (PLZ, Ort)",
|
||||||
'value': '',
|
"value": "",
|
||||||
'default_value': '',
|
"default_value": "",
|
||||||
'setting_type': 'text',
|
"setting_type": "text",
|
||||||
'category': 'corporate',
|
"category": "corporate",
|
||||||
'order': 6
|
"order": 6,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'corporate_phone',
|
"key": "corporate_phone",
|
||||||
'display_name': 'Telefonnummer',
|
"display_name": "Telefonnummer",
|
||||||
'description': 'Telefonnummer der Stiftung',
|
"description": "Telefonnummer der Stiftung",
|
||||||
'value': '',
|
"value": "",
|
||||||
'default_value': '',
|
"default_value": "",
|
||||||
'setting_type': 'text',
|
"setting_type": "text",
|
||||||
'category': 'corporate',
|
"category": "corporate",
|
||||||
'order': 7
|
"order": 7,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'corporate_email',
|
"key": "corporate_email",
|
||||||
'display_name': 'E-Mail-Adresse',
|
"display_name": "E-Mail-Adresse",
|
||||||
'description': 'Offizielle E-Mail-Adresse der Stiftung',
|
"description": "Offizielle E-Mail-Adresse der Stiftung",
|
||||||
'value': '',
|
"value": "",
|
||||||
'default_value': '',
|
"default_value": "",
|
||||||
'setting_type': 'text',
|
"setting_type": "text",
|
||||||
'category': 'corporate',
|
"category": "corporate",
|
||||||
'order': 8
|
"order": 8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'corporate_website',
|
"key": "corporate_website",
|
||||||
'display_name': 'Website',
|
"display_name": "Website",
|
||||||
'description': 'Website der Stiftung',
|
"description": "Website der Stiftung",
|
||||||
'value': '',
|
"value": "",
|
||||||
'default_value': '',
|
"default_value": "",
|
||||||
'setting_type': 'url',
|
"setting_type": "url",
|
||||||
'category': 'corporate',
|
"category": "corporate",
|
||||||
'order': 9
|
"order": 9,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': 'corporate_footer_text',
|
"key": "corporate_footer_text",
|
||||||
'display_name': 'Fußzeilen-Text',
|
"display_name": "Fußzeilen-Text",
|
||||||
'description': 'Text für die Fußzeile in PDF-Dokumenten',
|
"description": "Text für die Fußzeile in PDF-Dokumenten",
|
||||||
'value': 'Dieser Bericht wurde automatisch generiert.',
|
"value": "Dieser Bericht wurde automatisch generiert.",
|
||||||
'default_value': 'Dieser Bericht wurde automatisch generiert.',
|
"default_value": "Dieser Bericht wurde automatisch generiert.",
|
||||||
'setting_type': 'text',
|
"setting_type": "text",
|
||||||
'category': 'corporate',
|
"category": "corporate",
|
||||||
'order': 10
|
"order": 10,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -117,33 +119,32 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
for setting_data in corporate_settings:
|
for setting_data in corporate_settings:
|
||||||
setting, created = AppConfiguration.objects.get_or_create(
|
setting, created = AppConfiguration.objects.get_or_create(
|
||||||
key=setting_data['key'],
|
key=setting_data["key"], defaults=setting_data
|
||||||
defaults=setting_data
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
created_count += 1
|
created_count += 1
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(f'Created setting: {setting.display_name}')
|
self.style.SUCCESS(f"Created setting: {setting.display_name}")
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Update existing setting with new defaults if needed
|
# Update existing setting with new defaults if needed
|
||||||
if not setting.description:
|
if not setting.description:
|
||||||
setting.description = setting_data['description']
|
setting.description = setting_data["description"]
|
||||||
setting.save()
|
setting.save()
|
||||||
updated_count += 1
|
updated_count += 1
|
||||||
|
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(
|
self.style.SUCCESS(
|
||||||
f'Corporate identity settings initialized! '
|
f"Corporate identity settings initialized! "
|
||||||
f'Created {created_count} new settings, updated {updated_count} existing settings.'
|
f"Created {created_count} new settings, updated {updated_count} existing settings."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if created_count > 0:
|
if created_count > 0:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.WARNING(
|
self.style.WARNING(
|
||||||
'Please configure your corporate identity settings in '
|
"Please configure your corporate identity settings in "
|
||||||
'Administration -> Application Settings before generating PDFs.'
|
"Administration -> Application Settings before generating PDFs."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,84 +1,93 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db import transaction
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
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):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--dry-run',
|
"--dry-run",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Zeigt nur an, was gemacht würde, ohne Änderungen zu speichern',
|
help="Zeigt nur an, was gemacht würde, ohne Änderungen zu speichern",
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
dry_run = options['dry_run']
|
dry_run = options["dry_run"]
|
||||||
|
|
||||||
if 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
|
# Alle aktiven Verpachtungen finden
|
||||||
aktive_verpachtungen = Verpachtung.objects.filter(status='aktiv')
|
aktive_verpachtungen = Verpachtung.objects.filter(status="aktiv")
|
||||||
|
|
||||||
self.stdout.write(f'Gefunden: {aktive_verpachtungen.count()} aktive Verpachtungen')
|
self.stdout.write(
|
||||||
|
f"Gefunden: {aktive_verpachtungen.count()} aktive Verpachtungen"
|
||||||
|
)
|
||||||
|
|
||||||
migrated_count = 0
|
migrated_count = 0
|
||||||
skipped_count = 0
|
skipped_count = 0
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for verpachtung in aktive_verpachtungen:
|
for verpachtung in aktive_verpachtungen:
|
||||||
land = verpachtung.land
|
land = verpachtung.land
|
||||||
|
|
||||||
# Prüfen ob bereits migriert
|
# Prüfen ob bereits migriert
|
||||||
if land.aktueller_paechter is not None:
|
if land.aktueller_paechter is not None:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.WARNING(
|
self.style.WARNING(
|
||||||
f'Übersprungen: {land} hat bereits einen aktuellen Pächter'
|
f"Übersprungen: {land} hat bereits einen aktuellen Pächter"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
skipped_count += 1
|
skipped_count += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Migration durchführen
|
# Migration durchführen
|
||||||
self.stdout.write(f'Migriere: {land} -> {verpachtung.paechter}')
|
self.stdout.write(f"Migriere: {land} -> {verpachtung.paechter}")
|
||||||
|
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
# Pächter-Daten ins Land übertragen
|
# Pächter-Daten ins Land übertragen
|
||||||
land.aktueller_paechter = verpachtung.paechter
|
land.aktueller_paechter = verpachtung.paechter
|
||||||
land.paechter_name = verpachtung.paechter.get_full_name()
|
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.pachtbeginn = verpachtung.pachtbeginn
|
||||||
land.pachtende = verpachtung.pachtende
|
land.pachtende = verpachtung.pachtende
|
||||||
land.verlaengerung_klausel = bool(verpachtung.verlaengerung)
|
land.verlaengerung_klausel = bool(verpachtung.verlaengerung)
|
||||||
|
|
||||||
# Pachtzins übertragen
|
# Pachtzins übertragen
|
||||||
land.pachtzins_pauschal = verpachtung.pachtzins_jaehrlich
|
land.pachtzins_pauschal = verpachtung.pachtzins_jaehrlich
|
||||||
|
|
||||||
# Verpachtete Fläche aktualisieren (falls nicht gesetzt)
|
# Verpachtete Fläche aktualisieren (falls nicht gesetzt)
|
||||||
if land.verp_flaeche_aktuell == 0:
|
if land.verp_flaeche_aktuell == 0:
|
||||||
land.verp_flaeche_aktuell = verpachtung.verpachtete_flaeche
|
land.verp_flaeche_aktuell = verpachtung.verpachtete_flaeche
|
||||||
|
|
||||||
land.save()
|
land.save()
|
||||||
|
|
||||||
migrated_count += 1
|
migrated_count += 1
|
||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(
|
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:
|
else:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(
|
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):
|
def _get_paechter_anschrift(self, paechter):
|
||||||
"""Erstellt eine Anschrift aus den Pächter-Daten"""
|
"""Erstellt eine Anschrift aus den Pächter-Daten"""
|
||||||
parts = []
|
parts = []
|
||||||
@@ -88,5 +97,5 @@ class Command(BaseCommand):
|
|||||||
parts.append(f"{paechter.plz} {paechter.ort}")
|
parts.append(f"{paechter.plz} {paechter.ort}")
|
||||||
elif paechter.ort:
|
elif paechter.ort:
|
||||||
parts.append(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]
|
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.core.management.base import BaseCommand, CommandError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from decimal import Decimal
|
|
||||||
from datetime import date
|
from stiftung.models import LandAbrechnung, LandVerpachtung, Verpachtung
|
||||||
from stiftung.models import Verpachtung, LandVerpachtung, LandAbrechnung
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Synchronize existing Verpachtungen with LandAbrechnungen'
|
help = "Synchronize existing Verpachtungen with LandAbrechnungen"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--dry-run',
|
"--dry-run",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Show what would be done without making changes',
|
help="Show what would be done without making changes",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--year',
|
"--year",
|
||||||
type=int,
|
type=int,
|
||||||
help='Only sync data for specific year',
|
help="Only sync data for specific year",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--force',
|
"--force",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Force update even if Abrechnungen already exist',
|
help="Force update even if Abrechnungen already exist",
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
dry_run = options['dry_run']
|
dry_run = options["dry_run"]
|
||||||
target_year = options['year']
|
target_year = options["year"]
|
||||||
force = options['force']
|
force = options["force"]
|
||||||
|
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS('🔄 Starting Abrechnung synchronization...')
|
self.style.SUCCESS("🔄 Starting Abrechnung synchronization...")
|
||||||
)
|
)
|
||||||
|
|
||||||
if dry_run:
|
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
|
# Statistics
|
||||||
stats = {
|
stats = {
|
||||||
'legacy_contracts': 0,
|
"legacy_contracts": 0,
|
||||||
'new_contracts': 0,
|
"new_contracts": 0,
|
||||||
'abrechnungen_created': 0,
|
"abrechnungen_created": 0,
|
||||||
'abrechnungen_updated': 0,
|
"abrechnungen_updated": 0,
|
||||||
'total_rent_amount': Decimal('0.00'),
|
"total_rent_amount": Decimal("0.00"),
|
||||||
'years_processed': set(),
|
"years_processed": set(),
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
# Process Legacy Verpachtungen
|
# Process Legacy Verpachtungen
|
||||||
self.stdout.write('\n📄 Processing Legacy Verpachtungen...')
|
self.stdout.write("\n📄 Processing Legacy Verpachtungen...")
|
||||||
legacy_verpachtungen = Verpachtung.objects.all()
|
legacy_verpachtungen = Verpachtung.objects.all()
|
||||||
|
|
||||||
for verpachtung in legacy_verpachtungen:
|
for verpachtung in legacy_verpachtungen:
|
||||||
stats['legacy_contracts'] += 1
|
stats["legacy_contracts"] += 1
|
||||||
years_affected = self._get_affected_years(
|
years_affected = self._get_affected_years(
|
||||||
verpachtung.pachtbeginn,
|
verpachtung.pachtbeginn,
|
||||||
verpachtung.verlaengerung or verpachtung.pachtende,
|
verpachtung.verlaengerung or verpachtung.pachtende,
|
||||||
target_year
|
target_year,
|
||||||
)
|
)
|
||||||
|
|
||||||
for year in years_affected:
|
for year in years_affected:
|
||||||
stats['years_processed'].add(year)
|
stats["years_processed"].add(year)
|
||||||
rent_amount = self._calculate_legacy_rent_for_year(verpachtung, year)
|
rent_amount = self._calculate_legacy_rent_for_year(
|
||||||
|
verpachtung, year
|
||||||
|
)
|
||||||
|
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
created, updated = self._update_abrechnung(
|
created, updated = self._update_abrechnung(
|
||||||
verpachtung.land,
|
verpachtung.land,
|
||||||
year,
|
year,
|
||||||
rent_amount,
|
rent_amount,
|
||||||
Decimal('0.00'), # No umlage for legacy
|
Decimal("0.00"), # No umlage for legacy
|
||||||
f"Legacy-Verpachtung {verpachtung.vertragsnummer}",
|
f"Legacy-Verpachtung {verpachtung.vertragsnummer}",
|
||||||
force
|
force,
|
||||||
)
|
)
|
||||||
if created:
|
if created:
|
||||||
stats['abrechnungen_created'] += 1
|
stats["abrechnungen_created"] += 1
|
||||||
if updated:
|
if updated:
|
||||||
stats['abrechnungen_updated'] += 1
|
stats["abrechnungen_updated"] += 1
|
||||||
|
|
||||||
stats['total_rent_amount'] += rent_amount
|
stats["total_rent_amount"] += rent_amount
|
||||||
|
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
f" 📊 {verpachtung.vertragsnummer} ({year}): {rent_amount:.2f}€"
|
f" 📊 {verpachtung.vertragsnummer} ({year}): {rent_amount:.2f}€"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Process New LandVerpachtungen
|
# Process New LandVerpachtungen
|
||||||
self.stdout.write('\n🆕 Processing New LandVerpachtungen...')
|
self.stdout.write("\n🆕 Processing New LandVerpachtungen...")
|
||||||
land_verpachtungen = LandVerpachtung.objects.all()
|
land_verpachtungen = LandVerpachtung.objects.all()
|
||||||
|
|
||||||
for verpachtung in land_verpachtungen:
|
for verpachtung in land_verpachtungen:
|
||||||
stats['new_contracts'] += 1
|
stats["new_contracts"] += 1
|
||||||
years_affected = self._get_affected_years(
|
years_affected = self._get_affected_years(
|
||||||
verpachtung.pachtbeginn,
|
verpachtung.pachtbeginn, verpachtung.pachtende, target_year
|
||||||
verpachtung.pachtende,
|
|
||||||
target_year
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for year in years_affected:
|
for year in years_affected:
|
||||||
stats['years_processed'].add(year)
|
stats["years_processed"].add(year)
|
||||||
rent_amount = self._calculate_new_rent_for_year(verpachtung, year)
|
rent_amount = self._calculate_new_rent_for_year(
|
||||||
umlage_amount = Decimal('0.00') # To be calculated later
|
verpachtung, year
|
||||||
|
)
|
||||||
|
umlage_amount = Decimal("0.00") # To be calculated later
|
||||||
|
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
created, updated = self._update_abrechnung(
|
created, updated = self._update_abrechnung(
|
||||||
verpachtung.land,
|
verpachtung.land,
|
||||||
@@ -123,131 +129,143 @@ class Command(BaseCommand):
|
|||||||
rent_amount,
|
rent_amount,
|
||||||
umlage_amount,
|
umlage_amount,
|
||||||
f"LandVerpachtung {verpachtung.vertragsnummer}",
|
f"LandVerpachtung {verpachtung.vertragsnummer}",
|
||||||
force
|
force,
|
||||||
)
|
)
|
||||||
if created:
|
if created:
|
||||||
stats['abrechnungen_created'] += 1
|
stats["abrechnungen_created"] += 1
|
||||||
if updated:
|
if updated:
|
||||||
stats['abrechnungen_updated'] += 1
|
stats["abrechnungen_updated"] += 1
|
||||||
|
|
||||||
stats['total_rent_amount'] += rent_amount
|
stats["total_rent_amount"] += rent_amount
|
||||||
|
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
f" 📊 {verpachtung.vertragsnummer} ({year}): {rent_amount:.2f}€"
|
f" 📊 {verpachtung.vertragsnummer} ({year}): {rent_amount:.2f}€"
|
||||||
)
|
)
|
||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
# Rollback transaction in dry run
|
# Rollback transaction in dry run
|
||||||
transaction.set_rollback(True)
|
transaction.set_rollback(True)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.stdout.write(
|
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
|
# Print summary
|
||||||
self.stdout.write('\n' + '='*50)
|
self.stdout.write("\n" + "=" * 50)
|
||||||
self.stdout.write(self.style.SUCCESS('📈 SYNCHRONIZATION SUMMARY'))
|
self.stdout.write(self.style.SUCCESS("📈 SYNCHRONIZATION SUMMARY"))
|
||||||
self.stdout.write('='*50)
|
self.stdout.write("=" * 50)
|
||||||
self.stdout.write(f"Legacy contracts processed: {stats['legacy_contracts']}")
|
self.stdout.write(f"Legacy contracts processed: {stats['legacy_contracts']}")
|
||||||
self.stdout.write(f"New contracts processed: {stats['new_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 created: {stats['abrechnungen_created']}")
|
||||||
self.stdout.write(f"Abrechnungen updated: {stats['abrechnungen_updated']}")
|
self.stdout.write(f"Abrechnungen updated: {stats['abrechnungen_updated']}")
|
||||||
self.stdout.write(f"Total rent amount: {stats['total_rent_amount']:.2f}€")
|
self.stdout.write(f"Total rent amount: {stats['total_rent_amount']:.2f}€")
|
||||||
|
|
||||||
if dry_run:
|
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:
|
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):
|
def _get_affected_years(self, start_date, end_date, target_year=None):
|
||||||
"""Get all years affected by a contract"""
|
"""Get all years affected by a contract"""
|
||||||
if not start_date:
|
if not start_date:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
years = []
|
years = []
|
||||||
start_year = start_date.year
|
start_year = start_date.year
|
||||||
end_year = end_date.year if end_date else date.today().year
|
end_year = end_date.year if end_date else date.today().year
|
||||||
|
|
||||||
if target_year:
|
if target_year:
|
||||||
if start_year <= target_year <= end_year:
|
if start_year <= target_year <= end_year:
|
||||||
return [target_year]
|
return [target_year]
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
for year in range(start_year, end_year + 1):
|
for year in range(start_year, end_year + 1):
|
||||||
years.append(year)
|
years.append(year)
|
||||||
|
|
||||||
return years
|
return years
|
||||||
|
|
||||||
def _calculate_legacy_rent_for_year(self, verpachtung, year):
|
def _calculate_legacy_rent_for_year(self, verpachtung, year):
|
||||||
"""Calculate rent for legacy Verpachtung for specific year"""
|
"""Calculate rent for legacy Verpachtung for specific year"""
|
||||||
if not verpachtung.pachtzins_jaehrlich or not verpachtung.pachtbeginn:
|
if not verpachtung.pachtzins_jaehrlich or not verpachtung.pachtbeginn:
|
||||||
return Decimal('0.00')
|
return Decimal("0.00")
|
||||||
|
|
||||||
year_start = date(year, 1, 1)
|
year_start = date(year, 1, 1)
|
||||||
year_end = date(year, 12, 31)
|
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_start = max(verpachtung.pachtbeginn, year_start)
|
||||||
contract_end = min(contract_end_date or year_end, year_end)
|
contract_end = min(contract_end_date or year_end, year_end)
|
||||||
|
|
||||||
if contract_start > contract_end:
|
if contract_start > contract_end:
|
||||||
return Decimal('0.00')
|
return Decimal("0.00")
|
||||||
|
|
||||||
days_in_year = (year_end - year_start).days + 1
|
days_in_year = (year_end - year_start).days + 1
|
||||||
days_active = (contract_end - contract_start).days + 1
|
days_active = (contract_end - contract_start).days + 1
|
||||||
proportion = Decimal(str(days_active)) / Decimal(str(days_in_year))
|
proportion = Decimal(str(days_active)) / Decimal(str(days_in_year))
|
||||||
|
|
||||||
return Decimal(str(verpachtung.pachtzins_jaehrlich)) * proportion
|
return Decimal(str(verpachtung.pachtzins_jaehrlich)) * proportion
|
||||||
|
|
||||||
def _calculate_new_rent_for_year(self, verpachtung, year):
|
def _calculate_new_rent_for_year(self, verpachtung, year):
|
||||||
"""Calculate rent for new LandVerpachtung for specific year"""
|
"""Calculate rent for new LandVerpachtung for specific year"""
|
||||||
if not verpachtung.pachtzins_pauschal or not verpachtung.pachtbeginn:
|
if not verpachtung.pachtzins_pauschal or not verpachtung.pachtbeginn:
|
||||||
return Decimal('0.00')
|
return Decimal("0.00")
|
||||||
|
|
||||||
year_start = date(year, 1, 1)
|
year_start = date(year, 1, 1)
|
||||||
year_end = date(year, 12, 31)
|
year_end = date(year, 12, 31)
|
||||||
|
|
||||||
contract_start = max(verpachtung.pachtbeginn, year_start)
|
contract_start = max(verpachtung.pachtbeginn, year_start)
|
||||||
contract_end = min(verpachtung.pachtende or year_end, year_end)
|
contract_end = min(verpachtung.pachtende or year_end, year_end)
|
||||||
|
|
||||||
if contract_start > contract_end:
|
if contract_start > contract_end:
|
||||||
return Decimal('0.00')
|
return Decimal("0.00")
|
||||||
|
|
||||||
days_in_year = (year_end - year_start).days + 1
|
days_in_year = (year_end - year_start).days + 1
|
||||||
days_active = (contract_end - contract_start).days + 1
|
days_active = (contract_end - contract_start).days + 1
|
||||||
proportion = Decimal(str(days_active)) / Decimal(str(days_in_year))
|
proportion = Decimal(str(days_active)) / Decimal(str(days_in_year))
|
||||||
|
|
||||||
return Decimal(str(verpachtung.pachtzins_pauschal)) * proportion
|
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"""
|
"""Update or create Abrechnung for specific land and year"""
|
||||||
abrechnung, created = LandAbrechnung.objects.get_or_create(
|
abrechnung, created = LandAbrechnung.objects.get_or_create(
|
||||||
land=land,
|
land=land,
|
||||||
abrechnungsjahr=year,
|
abrechnungsjahr=year,
|
||||||
defaults={
|
defaults={
|
||||||
'pacht_vereinnahmt': rent_amount,
|
"pacht_vereinnahmt": rent_amount,
|
||||||
'umlagen_vereinnahmt': umlage_amount,
|
"umlagen_vereinnahmt": umlage_amount,
|
||||||
'bemerkungen': f'[{date.today().strftime("%d.%m.%Y")}] Automatisch synchronisiert von {source_note}'
|
"bemerkungen": f'[{date.today().strftime("%d.%m.%Y")}] Automatisch synchronisiert von {source_note}',
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
updated = False
|
updated = False
|
||||||
if not created and force:
|
if not created and force:
|
||||||
# Update existing
|
# Update existing
|
||||||
abrechnung.pacht_vereinnahmt += rent_amount
|
abrechnung.pacht_vereinnahmt += rent_amount
|
||||||
abrechnung.umlagen_vereinnahmt += umlage_amount
|
abrechnung.umlagen_vereinnahmt += umlage_amount
|
||||||
|
|
||||||
sync_note = f'[{date.today().strftime("%d.%m.%Y")}] Resync: +{rent_amount:.2f}€ von {source_note}'
|
sync_note = f'[{date.today().strftime("%d.%m.%Y")}] Resync: +{rent_amount:.2f}€ von {source_note}'
|
||||||
if abrechnung.bemerkungen:
|
if abrechnung.bemerkungen:
|
||||||
abrechnung.bemerkungen += f'\n{sync_note}'
|
abrechnung.bemerkungen += f"\n{sync_note}"
|
||||||
else:
|
else:
|
||||||
abrechnung.bemerkungen = sync_note
|
abrechnung.bemerkungen = sync_note
|
||||||
|
|
||||||
abrechnung.save()
|
abrechnung.save()
|
||||||
updated = True
|
updated = True
|
||||||
|
|
||||||
return created, updated
|
return created, updated
|
||||||
|
|||||||
@@ -1,111 +1,127 @@
|
|||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from stiftung.models import Land, Verpachtung, Paechter, LandAbrechnung
|
|
||||||
from datetime import datetime
|
from stiftung.models import Land, LandAbrechnung, Paechter, Verpachtung
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
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):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--dry-run',
|
"--dry-run",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Zeigt nur an, was gemacht würde, ohne Änderungen zu speichern',
|
help="Zeigt nur an, was gemacht würde, ohne Änderungen zu speichern",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--create-abrechnungen',
|
"--create-abrechnungen",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Erstellt automatisch Abrechnungen aus Verpachtungsdaten',
|
help="Erstellt automatisch Abrechnungen aus Verpachtungsdaten",
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
dry_run = options['dry_run']
|
dry_run = options["dry_run"]
|
||||||
create_abrechnungen = options['create_abrechnungen']
|
create_abrechnungen = options["create_abrechnungen"]
|
||||||
|
|
||||||
if 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!")
|
||||||
|
)
|
||||||
|
|
||||||
# Schritt 1: Alle Verpachtungen analysieren
|
# Schritt 1: Alle Verpachtungen analysieren
|
||||||
alle_verpachtungen = Verpachtung.objects.all().order_by('land', '-pachtbeginn')
|
alle_verpachtungen = Verpachtung.objects.all().order_by("land", "-pachtbeginn")
|
||||||
self.stdout.write(f'Gefunden: {alle_verpachtungen.count()} Verpachtungen insgesamt')
|
self.stdout.write(
|
||||||
|
f"Gefunden: {alle_verpachtungen.count()} Verpachtungen insgesamt"
|
||||||
|
)
|
||||||
|
|
||||||
land_updates = 0
|
land_updates = 0
|
||||||
abrechnungen_created = 0
|
abrechnungen_created = 0
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
current_land = None
|
current_land = None
|
||||||
|
|
||||||
for verpachtung in alle_verpachtungen:
|
for verpachtung in alle_verpachtungen:
|
||||||
land = verpachtung.land
|
land = verpachtung.land
|
||||||
|
|
||||||
# Für jedes Land nur die neueste aktive Verpachtung als "aktuell" setzen
|
# Für jedes Land nur die neueste aktive Verpachtung als "aktuell" setzen
|
||||||
if current_land != land:
|
if current_land != land:
|
||||||
current_land = land
|
current_land = land
|
||||||
|
|
||||||
# Prüfen ob dies die neueste aktive Verpachtung ist
|
# Prüfen ob dies die neueste aktive Verpachtung ist
|
||||||
if verpachtung.status == 'aktiv' and not land.aktueller_paechter:
|
if verpachtung.status == "aktiv" and not land.aktueller_paechter:
|
||||||
self.stdout.write(f'Setze aktuelle Verpachtung: {land} -> {verpachtung.paechter}')
|
self.stdout.write(
|
||||||
|
f"Setze aktuelle Verpachtung: {land} -> {verpachtung.paechter}"
|
||||||
|
)
|
||||||
|
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
# Land-Felder aktualisieren
|
# Land-Felder aktualisieren
|
||||||
land.aktueller_paechter = verpachtung.paechter
|
land.aktueller_paechter = verpachtung.paechter
|
||||||
land.paechter_name = verpachtung.paechter.get_full_name()
|
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.pachtbeginn = verpachtung.pachtbeginn
|
||||||
land.pachtende = verpachtung.pachtende
|
land.pachtende = verpachtung.pachtende
|
||||||
land.verlaengerung_klausel = bool(verpachtung.verlaengerung)
|
land.verlaengerung_klausel = bool(verpachtung.verlaengerung)
|
||||||
land.pachtzins_pauschal = verpachtung.pachtzins_jaehrlich
|
land.pachtzins_pauschal = verpachtung.pachtzins_jaehrlich
|
||||||
|
|
||||||
# Verpachtete Fläche synchronisieren
|
# Verpachtete Fläche synchronisieren
|
||||||
land.verp_flaeche_aktuell = verpachtung.verpachtete_flaeche
|
land.verp_flaeche_aktuell = verpachtung.verpachtete_flaeche
|
||||||
|
|
||||||
land.save()
|
land.save()
|
||||||
land_updates += 1
|
land_updates += 1
|
||||||
|
|
||||||
# Schritt 2: Abrechnungen aus Verpachtungen erstellen (optional)
|
# 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
|
# Erstelle Abrechnungen für die letzten 3 Jahre
|
||||||
current_year = datetime.now().year
|
current_year = datetime.now().year
|
||||||
for jahr in range(current_year - 2, current_year + 1):
|
for jahr in range(current_year - 2, current_year + 1):
|
||||||
|
|
||||||
# Prüfen ob Abrechnung bereits existiert
|
# Prüfen ob Abrechnung bereits existiert
|
||||||
existing = LandAbrechnung.objects.filter(
|
existing = LandAbrechnung.objects.filter(
|
||||||
land=land,
|
land=land, abrechnungsjahr=jahr
|
||||||
abrechnungsjahr=jahr
|
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not existing:
|
if not existing:
|
||||||
self.stdout.write(f'Erstelle Abrechnung: {land} - {jahr}')
|
self.stdout.write(f"Erstelle Abrechnung: {land} - {jahr}")
|
||||||
|
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
abrechnung = LandAbrechnung.objects.create(
|
abrechnung = LandAbrechnung.objects.create(
|
||||||
land=land,
|
land=land,
|
||||||
abrechnungsjahr=jahr,
|
abrechnungsjahr=jahr,
|
||||||
pacht_vereinnahmt=verpachtung.pachtzins_jaehrlich,
|
pacht_vereinnahmt=verpachtung.pachtzins_jaehrlich,
|
||||||
bemerkungen=f'Automatisch erstellt aus Verpachtung {verpachtung.vertragsnummer}'
|
bemerkungen=f"Automatisch erstellt aus Verpachtung {verpachtung.vertragsnummer}",
|
||||||
)
|
)
|
||||||
abrechnungen_created += 1
|
abrechnungen_created += 1
|
||||||
|
|
||||||
# Zusammenfassung
|
# Zusammenfassung
|
||||||
self.stdout.write(self.style.SUCCESS('\n=== MIGRATION ABGESCHLOSSEN ==='))
|
self.stdout.write(self.style.SUCCESS("\n=== MIGRATION ABGESCHLOSSEN ==="))
|
||||||
if dry_run:
|
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:
|
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:
|
else:
|
||||||
self.stdout.write(f'✓ {land_updates} Länder aktualisiert')
|
self.stdout.write(f"✓ {land_updates} Länder aktualisiert")
|
||||||
if create_abrechnungen:
|
if create_abrechnungen:
|
||||||
self.stdout.write(f'✓ {abrechnungen_created} Abrechnungen erstellt')
|
self.stdout.write(f"✓ {abrechnungen_created} Abrechnungen erstellt")
|
||||||
|
|
||||||
# Empfehlungen
|
# Empfehlungen
|
||||||
self.stdout.write(self.style.WARNING('\n=== NÄCHSTE SCHRITTE ==='))
|
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("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(
|
||||||
self.stdout.write('3. Neue Verpachtungen sollten direkt im Land-Model erstellt werden')
|
'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):
|
def _get_paechter_anschrift(self, paechter):
|
||||||
"""Erstellt eine Anschrift aus den Pächter-Daten"""
|
"""Erstellt eine Anschrift aus den Pächter-Daten"""
|
||||||
parts = []
|
parts = []
|
||||||
@@ -115,5 +131,5 @@ class Command(BaseCommand):
|
|||||||
parts.append(f"{paechter.plz} {paechter.ort}")
|
parts.append(f"{paechter.plz} {paechter.ort}")
|
||||||
elif paechter.ort:
|
elif paechter.ort:
|
||||||
parts.append(paechter.ort)
|
parts.append(paechter.ort)
|
||||||
|
|
||||||
return '\n'.join(parts) if parts else ''
|
return "\n".join(parts) if parts else ""
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ Automatically tracks all model changes throughout the application
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
|
||||||
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
||||||
from django.db.models.signals import post_save, post_delete, pre_save
|
from django.db.models.signals import post_delete, post_save, pre_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from stiftung.audit import log_action, track_model_changes, get_client_ip
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
|
|
||||||
|
from stiftung.audit import get_client_ip, log_action, track_model_changes
|
||||||
|
|
||||||
# Thread-local storage for request context
|
# Thread-local storage for request context
|
||||||
_local = threading.local()
|
_local = threading.local()
|
||||||
@@ -18,54 +20,54 @@ class AuditMiddleware(MiddlewareMixin):
|
|||||||
"""
|
"""
|
||||||
Middleware that sets up request context for audit logging
|
Middleware that sets up request context for audit logging
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
"""Store request in thread-local storage for access in signal handlers"""
|
"""Store request in thread-local storage for access in signal handlers"""
|
||||||
_local.request = request
|
_local.request = request
|
||||||
_local.user_changes = {} # Store pre-save state for change tracking
|
_local.user_changes = {} # Store pre-save state for change tracking
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
"""Clean up thread-local storage"""
|
"""Clean up thread-local storage"""
|
||||||
if hasattr(_local, 'request'):
|
if hasattr(_local, "request"):
|
||||||
delattr(_local, 'request')
|
delattr(_local, "request")
|
||||||
if hasattr(_local, 'user_changes'):
|
if hasattr(_local, "user_changes"):
|
||||||
delattr(_local, 'user_changes')
|
delattr(_local, "user_changes")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def get_current_request():
|
def get_current_request():
|
||||||
"""Get the current request from thread-local storage"""
|
"""Get the current request from thread-local storage"""
|
||||||
return getattr(_local, 'request', None)
|
return getattr(_local, "request", None)
|
||||||
|
|
||||||
|
|
||||||
def get_entity_type_from_model(model):
|
def get_entity_type_from_model(model):
|
||||||
"""Map Django model to audit entity type"""
|
"""Map Django model to audit entity type"""
|
||||||
model_name = model.__name__.lower()
|
model_name = model.__name__.lower()
|
||||||
|
|
||||||
mapping = {
|
mapping = {
|
||||||
'destinataer': 'destinataer',
|
"destinataer": "destinataer",
|
||||||
'land': 'land',
|
"land": "land",
|
||||||
'paechter': 'paechter',
|
"paechter": "paechter",
|
||||||
'verpachtung': 'verpachtung',
|
"verpachtung": "verpachtung",
|
||||||
'foerderung': 'foerderung',
|
"foerderung": "foerderung",
|
||||||
'rentmeister': 'rentmeister',
|
"rentmeister": "rentmeister",
|
||||||
'stiftungskonto': 'stiftungskonto',
|
"stiftungskonto": "stiftungskonto",
|
||||||
'verwaltungskosten': 'verwaltungskosten',
|
"verwaltungskosten": "verwaltungskosten",
|
||||||
'banktransaction': 'banktransaction',
|
"banktransaction": "banktransaction",
|
||||||
'dokumentlink': 'dokumentlink',
|
"dokumentlink": "dokumentlink",
|
||||||
'user': 'user',
|
"user": "user",
|
||||||
'person': 'destinataer', # Legacy model maps to destinataer
|
"person": "destinataer", # Legacy model maps to destinataer
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapping.get(model_name, 'unknown')
|
return mapping.get(model_name, "unknown")
|
||||||
|
|
||||||
|
|
||||||
def get_entity_name(instance):
|
def get_entity_name(instance):
|
||||||
"""Get a human-readable name for an entity"""
|
"""Get a human-readable name for an entity"""
|
||||||
if hasattr(instance, 'get_full_name') and callable(instance.get_full_name):
|
if hasattr(instance, "get_full_name") and callable(instance.get_full_name):
|
||||||
return instance.get_full_name()
|
return instance.get_full_name()
|
||||||
elif hasattr(instance, '__str__'):
|
elif hasattr(instance, "__str__"):
|
||||||
return str(instance)
|
return str(instance)
|
||||||
else:
|
else:
|
||||||
return f"{instance.__class__.__name__} #{instance.pk}"
|
return f"{instance.__class__.__name__} #{instance.pk}"
|
||||||
@@ -76,22 +78,22 @@ def get_entity_name(instance):
|
|||||||
def store_pre_save_state(sender, instance, **kwargs):
|
def store_pre_save_state(sender, instance, **kwargs):
|
||||||
"""Store the pre-save state for change tracking"""
|
"""Store the pre-save state for change tracking"""
|
||||||
request = get_current_request()
|
request = get_current_request()
|
||||||
if not request or not hasattr(request, 'user'):
|
if not request or not hasattr(request, "user"):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Skip if user is not authenticated
|
# Skip if user is not authenticated
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Skip audit log entries themselves to avoid infinite loops
|
# Skip audit log entries themselves to avoid infinite loops
|
||||||
if sender.__name__ == 'AuditLog':
|
if sender.__name__ == "AuditLog":
|
||||||
return
|
return
|
||||||
|
|
||||||
# Store the current state if this is an update
|
# Store the current state if this is an update
|
||||||
if instance.pk:
|
if instance.pk:
|
||||||
try:
|
try:
|
||||||
old_instance = sender.objects.get(pk=instance.pk)
|
old_instance = sender.objects.get(pk=instance.pk)
|
||||||
if not hasattr(_local, 'user_changes'):
|
if not hasattr(_local, "user_changes"):
|
||||||
_local.user_changes = {}
|
_local.user_changes = {}
|
||||||
_local.user_changes[instance.pk] = old_instance
|
_local.user_changes[instance.pk] = old_instance
|
||||||
except sender.DoesNotExist:
|
except sender.DoesNotExist:
|
||||||
@@ -102,53 +104,53 @@ def store_pre_save_state(sender, instance, **kwargs):
|
|||||||
def log_model_save(sender, instance, created, **kwargs):
|
def log_model_save(sender, instance, created, **kwargs):
|
||||||
"""Log model creation and updates"""
|
"""Log model creation and updates"""
|
||||||
request = get_current_request()
|
request = get_current_request()
|
||||||
if not request or not hasattr(request, 'user'):
|
if not request or not hasattr(request, "user"):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Skip if user is not authenticated
|
# Skip if user is not authenticated
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Skip audit log entries themselves to avoid infinite loops
|
# Skip audit log entries themselves to avoid infinite loops
|
||||||
if sender.__name__ == 'AuditLog':
|
if sender.__name__ == "AuditLog":
|
||||||
return
|
return
|
||||||
|
|
||||||
# Skip certain system models
|
# Skip certain system models
|
||||||
if sender.__name__ in ['Session', 'LogEntry', 'ContentType', 'Permission']:
|
if sender.__name__ in ["Session", "LogEntry", "ContentType", "Permission"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
entity_type = get_entity_type_from_model(sender)
|
entity_type = get_entity_type_from_model(sender)
|
||||||
entity_name = get_entity_name(instance)
|
entity_name = get_entity_name(instance)
|
||||||
entity_id = str(instance.pk)
|
entity_id = str(instance.pk)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
# Log creation
|
# Log creation
|
||||||
description = f"Neue {entity_type.replace('_', ' ').title()} '{entity_name}' wurde erstellt"
|
description = f"Neue {entity_type.replace('_', ' ').title()} '{entity_name}' wurde erstellt"
|
||||||
log_action(
|
log_action(
|
||||||
request=request,
|
request=request,
|
||||||
action='create',
|
action="create",
|
||||||
entity_type=entity_type,
|
entity_type=entity_type,
|
||||||
entity_id=entity_id,
|
entity_id=entity_id,
|
||||||
entity_name=entity_name,
|
entity_name=entity_name,
|
||||||
description=description
|
description=description,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Log update with changes
|
# Log update with changes
|
||||||
changes = {}
|
changes = {}
|
||||||
if hasattr(_local, 'user_changes') and instance.pk in _local.user_changes:
|
if hasattr(_local, "user_changes") and instance.pk in _local.user_changes:
|
||||||
old_instance = _local.user_changes[instance.pk]
|
old_instance = _local.user_changes[instance.pk]
|
||||||
changes = track_model_changes(old_instance, instance)
|
changes = track_model_changes(old_instance, instance)
|
||||||
|
|
||||||
if changes: # Only log if there are actual changes
|
if changes: # Only log if there are actual changes
|
||||||
description = f"{entity_type.replace('_', ' ').title()} '{entity_name}' wurde aktualisiert"
|
description = f"{entity_type.replace('_', ' ').title()} '{entity_name}' wurde aktualisiert"
|
||||||
log_action(
|
log_action(
|
||||||
request=request,
|
request=request,
|
||||||
action='update',
|
action="update",
|
||||||
entity_type=entity_type,
|
entity_type=entity_type,
|
||||||
entity_id=entity_id,
|
entity_id=entity_id,
|
||||||
entity_name=entity_name,
|
entity_name=entity_name,
|
||||||
description=description,
|
description=description,
|
||||||
changes=changes
|
changes=changes,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -156,33 +158,35 @@ def log_model_save(sender, instance, created, **kwargs):
|
|||||||
def log_model_delete(sender, instance, **kwargs):
|
def log_model_delete(sender, instance, **kwargs):
|
||||||
"""Log model deletion"""
|
"""Log model deletion"""
|
||||||
request = get_current_request()
|
request = get_current_request()
|
||||||
if not request or not hasattr(request, 'user'):
|
if not request or not hasattr(request, "user"):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Skip if user is not authenticated
|
# Skip if user is not authenticated
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Skip audit log entries themselves
|
# Skip audit log entries themselves
|
||||||
if sender.__name__ == 'AuditLog':
|
if sender.__name__ == "AuditLog":
|
||||||
return
|
return
|
||||||
|
|
||||||
# Skip certain system models
|
# Skip certain system models
|
||||||
if sender.__name__ in ['Session', 'LogEntry', 'ContentType', 'Permission']:
|
if sender.__name__ in ["Session", "LogEntry", "ContentType", "Permission"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
entity_type = get_entity_type_from_model(sender)
|
entity_type = get_entity_type_from_model(sender)
|
||||||
entity_name = get_entity_name(instance)
|
entity_name = get_entity_name(instance)
|
||||||
entity_id = str(instance.pk)
|
entity_id = str(instance.pk)
|
||||||
|
|
||||||
description = f"{entity_type.replace('_', ' ').title()} '{entity_name}' wurde gelöscht"
|
description = (
|
||||||
|
f"{entity_type.replace('_', ' ').title()} '{entity_name}' wurde gelöscht"
|
||||||
|
)
|
||||||
log_action(
|
log_action(
|
||||||
request=request,
|
request=request,
|
||||||
action='delete',
|
action="delete",
|
||||||
entity_type=entity_type,
|
entity_type=entity_type,
|
||||||
entity_id=entity_id,
|
entity_id=entity_id,
|
||||||
entity_name=entity_name,
|
entity_name=entity_name,
|
||||||
description=description
|
description=description,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -192,11 +196,11 @@ def log_user_login(sender, request, user, **kwargs):
|
|||||||
"""Log user login"""
|
"""Log user login"""
|
||||||
log_action(
|
log_action(
|
||||||
request=request,
|
request=request,
|
||||||
action='login',
|
action="login",
|
||||||
entity_type='user',
|
entity_type="user",
|
||||||
entity_id=str(user.pk),
|
entity_id=str(user.pk),
|
||||||
entity_name=user.username,
|
entity_name=user.username,
|
||||||
description=f"Benutzer {user.username} hat sich angemeldet"
|
description=f"Benutzer {user.username} hat sich angemeldet",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -206,9 +210,9 @@ def log_user_logout(sender, request, user, **kwargs):
|
|||||||
if user: # user might be None if session expired
|
if user: # user might be None if session expired
|
||||||
log_action(
|
log_action(
|
||||||
request=request,
|
request=request,
|
||||||
action='logout',
|
action="logout",
|
||||||
entity_type='user',
|
entity_type="user",
|
||||||
entity_id=str(user.pk),
|
entity_id=str(user.pk),
|
||||||
entity_name=user.username,
|
entity_name=user.username,
|
||||||
description=f"Benutzer {user.username} hat sich abgemeldet"
|
description=f"Benutzer {user.username} hat sich abgemeldet",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-08-13 20:59
|
# Generated by Django 5.0.6 on 2025-08-13 20:59
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
@@ -9,39 +10,76 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = []
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='DokumentLink',
|
name="DokumentLink",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('paperless_document_id', models.IntegerField()),
|
"id",
|
||||||
('kontext', models.CharField(max_length=30)),
|
models.UUIDField(
|
||||||
('titel', models.CharField(max_length=255)),
|
default=uuid.uuid4,
|
||||||
|
editable=False,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("paperless_document_id", models.IntegerField()),
|
||||||
|
("kontext", models.CharField(max_length=30)),
|
||||||
|
("titel", models.CharField(max_length=255)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Person',
|
name="Person",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('familienzweig', models.CharField(max_length=100)),
|
"id",
|
||||||
('vorname', models.CharField(max_length=100)),
|
models.UUIDField(
|
||||||
('nachname', models.CharField(max_length=100)),
|
default=uuid.uuid4,
|
||||||
('geburtsdatum', models.DateField(blank=True, null=True)),
|
editable=False,
|
||||||
('email', models.EmailField(blank=True, max_length=254, null=True)),
|
primary_key=True,
|
||||||
('iban', models.CharField(blank=True, max_length=34, null=True)),
|
serialize=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("familienzweig", models.CharField(max_length=100)),
|
||||||
|
("vorname", models.CharField(max_length=100)),
|
||||||
|
("nachname", models.CharField(max_length=100)),
|
||||||
|
("geburtsdatum", models.DateField(blank=True, null=True)),
|
||||||
|
("email", models.EmailField(blank=True, max_length=254, null=True)),
|
||||||
|
("iban", models.CharField(blank=True, max_length=34, null=True)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Foerderung',
|
name="Foerderung",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('jahr', models.IntegerField()),
|
"id",
|
||||||
('betrag', models.DecimalField(decimal_places=2, max_digits=12)),
|
models.UUIDField(
|
||||||
('verwendungsnachweis', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.dokumentlink')),
|
default=uuid.uuid4,
|
||||||
('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='stiftung.person')),
|
editable=False,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("jahr", models.IntegerField()),
|
||||||
|
("betrag", models.DecimalField(decimal_places=2, max_digits=12)),
|
||||||
|
(
|
||||||
|
"verwendungsnachweis",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="stiftung.dokumentlink",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"person",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="stiftung.person",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,104 +9,172 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0001_initial'),
|
("stiftung", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='dokumentlink',
|
name="dokumentlink",
|
||||||
options={'ordering': ['titel'], 'verbose_name': 'Dokument', 'verbose_name_plural': 'Dokumente'},
|
options={
|
||||||
|
"ordering": ["titel"],
|
||||||
|
"verbose_name": "Dokument",
|
||||||
|
"verbose_name_plural": "Dokumente",
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='foerderung',
|
name="foerderung",
|
||||||
options={'ordering': ['-jahr', '-betrag'], 'verbose_name': 'Förderung', 'verbose_name_plural': 'Förderungen'},
|
options={
|
||||||
|
"ordering": ["-jahr", "-betrag"],
|
||||||
|
"verbose_name": "Förderung",
|
||||||
|
"verbose_name_plural": "Förderungen",
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='person',
|
name="person",
|
||||||
options={'ordering': ['nachname', 'vorname'], 'verbose_name': 'Person', 'verbose_name_plural': 'Personen'},
|
options={
|
||||||
|
"ordering": ["nachname", "vorname"],
|
||||||
|
"verbose_name": "Person",
|
||||||
|
"verbose_name_plural": "Personen",
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='beschreibung',
|
name="beschreibung",
|
||||||
field=models.TextField(blank=True, null=True),
|
field=models.TextField(blank=True, null=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='foerderung',
|
model_name="foerderung",
|
||||||
name='antragsdatum',
|
name="antragsdatum",
|
||||||
field=models.DateField(default=django.utils.timezone.now),
|
field=models.DateField(default=django.utils.timezone.now),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='foerderung',
|
model_name="foerderung",
|
||||||
name='bemerkungen',
|
name="bemerkungen",
|
||||||
field=models.TextField(blank=True, null=True),
|
field=models.TextField(blank=True, null=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='foerderung',
|
model_name="foerderung",
|
||||||
name='entscheidungsdatum',
|
name="entscheidungsdatum",
|
||||||
field=models.DateField(blank=True, null=True),
|
field=models.DateField(blank=True, null=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='foerderung',
|
model_name="foerderung",
|
||||||
name='kategorie',
|
name="kategorie",
|
||||||
field=models.CharField(choices=[('bildung', 'Bildung'), ('forschung', 'Forschung'), ('kultur', 'Kultur'), ('soziales', 'Soziales'), ('umwelt', 'Umwelt'), ('anderes', 'Anderes')], default='anderes', max_length=20),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("bildung", "Bildung"),
|
||||||
|
("forschung", "Forschung"),
|
||||||
|
("kultur", "Kultur"),
|
||||||
|
("soziales", "Soziales"),
|
||||||
|
("umwelt", "Umwelt"),
|
||||||
|
("anderes", "Anderes"),
|
||||||
|
],
|
||||||
|
default="anderes",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='foerderung',
|
model_name="foerderung",
|
||||||
name='status',
|
name="status",
|
||||||
field=models.CharField(choices=[('beantragt', 'Beantragt'), ('genehmigt', 'Genehmigt'), ('ausgezahlt', 'Ausgezahlt'), ('abgelehnt', 'Abgelehnt'), ('storniert', 'Storniert')], default='beantragt', max_length=20),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("beantragt", "Beantragt"),
|
||||||
|
("genehmigt", "Genehmigt"),
|
||||||
|
("ausgezahlt", "Ausgezahlt"),
|
||||||
|
("abgelehnt", "Abgelehnt"),
|
||||||
|
("storniert", "Storniert"),
|
||||||
|
],
|
||||||
|
default="beantragt",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='person',
|
model_name="person",
|
||||||
name='adresse',
|
name="adresse",
|
||||||
field=models.TextField(blank=True, null=True),
|
field=models.TextField(blank=True, null=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='person',
|
model_name="person",
|
||||||
name='aktiv',
|
name="aktiv",
|
||||||
field=models.BooleanField(default=True),
|
field=models.BooleanField(default=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='person',
|
model_name="person",
|
||||||
name='notizen',
|
name="notizen",
|
||||||
field=models.TextField(blank=True, null=True),
|
field=models.TextField(blank=True, null=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='person',
|
model_name="person",
|
||||||
name='telefon',
|
name="telefon",
|
||||||
field=models.CharField(blank=True, max_length=20, null=True),
|
field=models.CharField(blank=True, max_length=20, null=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='kontext',
|
name="kontext",
|
||||||
field=models.CharField(choices=[('antrag', 'Antrag'), ('verwendungsnachweis', 'Verwendungsnachweis'), ('rechnung', 'Rechnung'), ('vertrag', 'Vertrag'), ('bericht', 'Bericht'), ('anderes', 'Anderes')], default='anderes', max_length=30),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("antrag", "Antrag"),
|
||||||
|
("verwendungsnachweis", "Verwendungsnachweis"),
|
||||||
|
("rechnung", "Rechnung"),
|
||||||
|
("vertrag", "Vertrag"),
|
||||||
|
("bericht", "Bericht"),
|
||||||
|
("anderes", "Anderes"),
|
||||||
|
],
|
||||||
|
default="anderes",
|
||||||
|
max_length=30,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='paperless_document_id',
|
name="paperless_document_id",
|
||||||
field=models.IntegerField(unique=True),
|
field=models.IntegerField(unique=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='foerderung',
|
model_name="foerderung",
|
||||||
name='jahr',
|
name="jahr",
|
||||||
field=models.IntegerField(validators=[django.core.validators.MinValueValidator(1900), django.core.validators.MaxValueValidator(2100)]),
|
field=models.IntegerField(
|
||||||
|
validators=[
|
||||||
|
django.core.validators.MinValueValidator(1900),
|
||||||
|
django.core.validators.MaxValueValidator(2100),
|
||||||
|
]
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='foerderung',
|
model_name="foerderung",
|
||||||
name='person',
|
name="person",
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='stiftung.person', verbose_name='Person'),
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="stiftung.person",
|
||||||
|
verbose_name="Person",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='foerderung',
|
model_name="foerderung",
|
||||||
name='verwendungsnachweis',
|
name="verwendungsnachweis",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.dokumentlink', verbose_name='Verwendungsnachweis'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="stiftung.dokumentlink",
|
||||||
|
verbose_name="Verwendungsnachweis",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='person',
|
model_name="person",
|
||||||
name='familienzweig',
|
name="familienzweig",
|
||||||
field=models.CharField(choices=[('hauptzweig', 'Hauptzweig'), ('nebenzweig', 'Nebenzweig'), ('verwandt', 'Verwandt'), ('anderer', 'Anderer')], default='hauptzweig', max_length=100),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("hauptzweig", "Hauptzweig"),
|
||||||
|
("nebenzweig", "Nebenzweig"),
|
||||||
|
("verwandt", "Verwandt"),
|
||||||
|
("anderer", "Anderer"),
|
||||||
|
],
|
||||||
|
default="hauptzweig",
|
||||||
|
max_length=100,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='foerderung',
|
name="foerderung",
|
||||||
unique_together={('person', 'jahr', 'kategorie')},
|
unique_together={("person", "jahr", "kategorie")},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,78 +1,293 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-08-13 21:43
|
# Generated by Django 5.0.6 on 2025-08-13 21:43
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import uuid
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0002_alter_dokumentlink_options_alter_foerderung_options_and_more'),
|
(
|
||||||
|
"stiftung",
|
||||||
|
"0002_alter_dokumentlink_options_alter_foerderung_options_and_more",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Land',
|
name="Land",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('lfd_nr', models.CharField(max_length=20, unique=True, verbose_name='Lfd. Nr.')),
|
"id",
|
||||||
('ew_nummer', models.CharField(blank=True, max_length=50, null=True, verbose_name='EW-Nummer')),
|
models.UUIDField(
|
||||||
('amtsgericht', models.CharField(max_length=100, verbose_name='Amtsgericht')),
|
default=uuid.uuid4,
|
||||||
('gemeinde', models.CharField(max_length=100, verbose_name='Gemeinde')),
|
editable=False,
|
||||||
('gemarkung', models.CharField(max_length=100, verbose_name='Gemarkung')),
|
primary_key=True,
|
||||||
('flur', models.CharField(max_length=50, verbose_name='Flur')),
|
serialize=False,
|
||||||
('flurstueck', models.CharField(max_length=50, verbose_name='Flurstück')),
|
),
|
||||||
('groesse_qm', models.DecimalField(decimal_places=2, max_digits=12, validators=[django.core.validators.MinValueValidator(0.01)], verbose_name='Größe in qm')),
|
),
|
||||||
('gruenland_qm', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Grünland (qm)')),
|
(
|
||||||
('acker_qm', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Acker (qm)')),
|
"lfd_nr",
|
||||||
('wald_qm', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Wald (qm)')),
|
models.CharField(
|
||||||
('sonstiges_qm', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Sonstiges (qm)')),
|
max_length=20, unique=True, verbose_name="Lfd. Nr."
|
||||||
('verpachtete_gesamtflaeche', models.DecimalField(decimal_places=2, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Verpachtete Gesamtfläche (qm)')),
|
),
|
||||||
('flaeche_alte_liste', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, verbose_name='Fläche alte Liste (qm)')),
|
),
|
||||||
('verp_flaeche_aktuell', models.DecimalField(decimal_places=2, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Verp. Fläche aktuell (qm)')),
|
(
|
||||||
('anteil_grundsteuer', models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True, verbose_name='Anteil Grundsteuer (%)')),
|
"ew_nummer",
|
||||||
('anteil_lwk', models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True, verbose_name='Anteil LWK (%)')),
|
models.CharField(
|
||||||
('aktiv', models.BooleanField(default=True, verbose_name='Aktiv')),
|
blank=True, max_length=50, null=True, verbose_name="EW-Nummer"
|
||||||
('notizen', models.TextField(blank=True, null=True, verbose_name='Ergänzende Kommentare')),
|
),
|
||||||
('erstellt_am', models.DateTimeField(auto_now_add=True)),
|
),
|
||||||
('aktualisiert_am', models.DateTimeField(auto_now=True)),
|
(
|
||||||
|
"amtsgericht",
|
||||||
|
models.CharField(max_length=100, verbose_name="Amtsgericht"),
|
||||||
|
),
|
||||||
|
("gemeinde", models.CharField(max_length=100, verbose_name="Gemeinde")),
|
||||||
|
(
|
||||||
|
"gemarkung",
|
||||||
|
models.CharField(max_length=100, verbose_name="Gemarkung"),
|
||||||
|
),
|
||||||
|
("flur", models.CharField(max_length=50, verbose_name="Flur")),
|
||||||
|
(
|
||||||
|
"flurstueck",
|
||||||
|
models.CharField(max_length=50, verbose_name="Flurstück"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"groesse_qm",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0.01)],
|
||||||
|
verbose_name="Größe in qm",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"gruenland_qm",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Grünland (qm)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"acker_qm",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Acker (qm)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"wald_qm",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Wald (qm)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"sonstiges_qm",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Sonstiges (qm)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"verpachtete_gesamtflaeche",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Verpachtete Gesamtfläche (qm)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"flaeche_alte_liste",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Fläche alte Liste (qm)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"verp_flaeche_aktuell",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Verp. Fläche aktuell (qm)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"anteil_grundsteuer",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=8,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Anteil Grundsteuer (%)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"anteil_lwk",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=8,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Anteil LWK (%)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("aktiv", models.BooleanField(default=True, verbose_name="Aktiv")),
|
||||||
|
(
|
||||||
|
"notizen",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="Ergänzende Kommentare"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("erstellt_am", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("aktualisiert_am", models.DateTimeField(auto_now=True)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Land',
|
"verbose_name": "Land",
|
||||||
'verbose_name_plural': 'Ländereien',
|
"verbose_name_plural": "Ländereien",
|
||||||
'ordering': ['gemeinde', 'gemarkung', 'flur', 'flurstueck'],
|
"ordering": ["gemeinde", "gemarkung", "flur", "flurstueck"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='kontext',
|
name="kontext",
|
||||||
field=models.CharField(choices=[('antrag', 'Antrag'), ('verwendungsnachweis', 'Verwendungsnachweis'), ('rechnung', 'Rechnung'), ('vertrag', 'Vertrag'), ('bericht', 'Bericht'), ('landkarte', 'Landkarte'), ('kataster', 'Kataster'), ('anderes', 'Anderes')], default='anderes', max_length=30),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("antrag", "Antrag"),
|
||||||
|
("verwendungsnachweis", "Verwendungsnachweis"),
|
||||||
|
("rechnung", "Rechnung"),
|
||||||
|
("vertrag", "Vertrag"),
|
||||||
|
("bericht", "Bericht"),
|
||||||
|
("landkarte", "Landkarte"),
|
||||||
|
("kataster", "Kataster"),
|
||||||
|
("anderes", "Anderes"),
|
||||||
|
],
|
||||||
|
default="anderes",
|
||||||
|
max_length=30,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Verpachtung',
|
name="Verpachtung",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('vertragsnummer', models.CharField(max_length=50, unique=True, verbose_name='Vertragsnummer')),
|
"id",
|
||||||
('pachtbeginn', models.DateField(verbose_name='Pachtbeginn')),
|
models.UUIDField(
|
||||||
('pachtende', models.DateField(verbose_name='Pachtende')),
|
default=uuid.uuid4,
|
||||||
('verlaengerung', models.DateField(blank=True, null=True, verbose_name='Verlängerung bis')),
|
editable=False,
|
||||||
('pachtzins_pro_qm', models.DecimalField(decimal_places=4, max_digits=8, verbose_name='Pachtzins pro qm (€)')),
|
primary_key=True,
|
||||||
('pachtzins_jaehrlich', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Jährlicher Pachtzins (€)')),
|
serialize=False,
|
||||||
('verpachtete_flaeche', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Verpachtete Fläche (qm)')),
|
),
|
||||||
('status', models.CharField(choices=[('aktiv', 'Aktiv'), ('beendet', 'Beendet'), ('gekuendigt', 'Gekündigt'), ('verlängert', 'Verlängert')], default='aktiv', max_length=20)),
|
),
|
||||||
('bemerkungen', models.TextField(blank=True, null=True, verbose_name='Ergänzende Kommentare')),
|
(
|
||||||
('erstellt_am', models.DateTimeField(auto_now_add=True)),
|
"vertragsnummer",
|
||||||
('aktualisiert_am', models.DateTimeField(auto_now=True)),
|
models.CharField(
|
||||||
('land', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='stiftung.land', verbose_name='Land')),
|
max_length=50, unique=True, verbose_name="Vertragsnummer"
|
||||||
('paechter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='stiftung.person', verbose_name='Pächter')),
|
),
|
||||||
('verwendungsnachweis', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.dokumentlink', verbose_name='Verwendungsnachweis')),
|
),
|
||||||
|
("pachtbeginn", models.DateField(verbose_name="Pachtbeginn")),
|
||||||
|
("pachtende", models.DateField(verbose_name="Pachtende")),
|
||||||
|
(
|
||||||
|
"verlaengerung",
|
||||||
|
models.DateField(
|
||||||
|
blank=True, null=True, verbose_name="Verlängerung bis"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pachtzins_pro_qm",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=4,
|
||||||
|
max_digits=8,
|
||||||
|
verbose_name="Pachtzins pro qm (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pachtzins_jaehrlich",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
verbose_name="Jährlicher Pachtzins (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"verpachtete_flaeche",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
verbose_name="Verpachtete Fläche (qm)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("aktiv", "Aktiv"),
|
||||||
|
("beendet", "Beendet"),
|
||||||
|
("gekuendigt", "Gekündigt"),
|
||||||
|
("verlängert", "Verlängert"),
|
||||||
|
],
|
||||||
|
default="aktiv",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"bemerkungen",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="Ergänzende Kommentare"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("erstellt_am", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("aktualisiert_am", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"land",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="stiftung.land",
|
||||||
|
verbose_name="Land",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"paechter",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="stiftung.person",
|
||||||
|
verbose_name="Pächter",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"verwendungsnachweis",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="stiftung.dokumentlink",
|
||||||
|
verbose_name="Verwendungsnachweis",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Verpachtung',
|
"verbose_name": "Verpachtung",
|
||||||
'verbose_name_plural': 'Verpachtungen',
|
"verbose_name_plural": "Verpachtungen",
|
||||||
'ordering': ['-pachtbeginn'],
|
"ordering": ["-pachtbeginn"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,36 +1,106 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-08-13 22:18
|
# Generated by Django 5.0.6 on 2025-08-13 22:18
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0003_land_alter_dokumentlink_kontext_verpachtung'),
|
("stiftung", "0003_land_alter_dokumentlink_kontext_verpachtung"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='CSVImport',
|
name="CSVImport",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('import_type', models.CharField(choices=[('personen', 'Personen'), ('laendereien', 'Ländereien'), ('verpachtungen', 'Verpachtungen')], max_length=20, verbose_name='Import-Typ')),
|
"id",
|
||||||
('filename', models.CharField(max_length=255, verbose_name='Dateiname')),
|
models.UUIDField(
|
||||||
('file_size', models.IntegerField(verbose_name='Dateigröße (Bytes)')),
|
default=uuid.uuid4,
|
||||||
('status', models.CharField(choices=[('pending', 'Ausstehend'), ('processing', 'Wird verarbeitet'), ('completed', 'Abgeschlossen'), ('failed', 'Fehlgeschlagen'), ('partial', 'Teilweise erfolgreich')], default='pending', max_length=20)),
|
editable=False,
|
||||||
('total_rows', models.IntegerField(default=0, verbose_name='Gesamtzeilen')),
|
primary_key=True,
|
||||||
('imported_rows', models.IntegerField(default=0, verbose_name='Importierte Zeilen')),
|
serialize=False,
|
||||||
('failed_rows', models.IntegerField(default=0, verbose_name='Fehlgeschlagene Zeilen')),
|
),
|
||||||
('error_log', models.TextField(blank=True, null=True, verbose_name='Fehlerprotokoll')),
|
),
|
||||||
('created_by', models.CharField(blank=True, max_length=100, null=True, verbose_name='Erstellt von')),
|
(
|
||||||
('started_at', models.DateTimeField(auto_now_add=True, verbose_name='Gestartet um')),
|
"import_type",
|
||||||
('completed_at', models.DateTimeField(blank=True, null=True, verbose_name='Abgeschlossen um')),
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("personen", "Personen"),
|
||||||
|
("laendereien", "Ländereien"),
|
||||||
|
("verpachtungen", "Verpachtungen"),
|
||||||
|
],
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Import-Typ",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"filename",
|
||||||
|
models.CharField(max_length=255, verbose_name="Dateiname"),
|
||||||
|
),
|
||||||
|
("file_size", models.IntegerField(verbose_name="Dateigröße (Bytes)")),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("pending", "Ausstehend"),
|
||||||
|
("processing", "Wird verarbeitet"),
|
||||||
|
("completed", "Abgeschlossen"),
|
||||||
|
("failed", "Fehlgeschlagen"),
|
||||||
|
("partial", "Teilweise erfolgreich"),
|
||||||
|
],
|
||||||
|
default="pending",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"total_rows",
|
||||||
|
models.IntegerField(default=0, verbose_name="Gesamtzeilen"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"imported_rows",
|
||||||
|
models.IntegerField(default=0, verbose_name="Importierte Zeilen"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"failed_rows",
|
||||||
|
models.IntegerField(
|
||||||
|
default=0, verbose_name="Fehlgeschlagene Zeilen"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"error_log",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="Fehlerprotokoll"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created_by",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=100,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Erstellt von",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"started_at",
|
||||||
|
models.DateTimeField(
|
||||||
|
auto_now_add=True, verbose_name="Gestartet um"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"completed_at",
|
||||||
|
models.DateTimeField(
|
||||||
|
blank=True, null=True, verbose_name="Abgeschlossen um"
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'CSV Import',
|
"verbose_name": "CSV Import",
|
||||||
'verbose_name_plural': 'CSV Imports',
|
"verbose_name_plural": "CSV Imports",
|
||||||
'ordering': ['-started_at'],
|
"ordering": ["-started_at"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,93 +1,298 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-08-14 10:38
|
# Generated by Django 5.0.6 on 2025-08-14 10:38
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0004_csvimport'),
|
("stiftung", "0004_csvimport"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Destinataer',
|
name="Destinataer",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('familienzweig', models.CharField(choices=[('hauptzweig', 'Hauptzweig'), ('nebenzweig', 'Nebenzweig'), ('verwandt', 'Verwandt'), ('anderer', 'Anderer')], default='hauptzweig', max_length=100)),
|
"id",
|
||||||
('vorname', models.CharField(max_length=100, verbose_name='Vorname')),
|
models.UUIDField(
|
||||||
('nachname', models.CharField(max_length=100, verbose_name='Nachname')),
|
default=uuid.uuid4,
|
||||||
('geburtsdatum', models.DateField(blank=True, null=True, verbose_name='Geburtsdatum')),
|
editable=False,
|
||||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='E-Mail')),
|
primary_key=True,
|
||||||
('telefon', models.CharField(blank=True, max_length=20, null=True, verbose_name='Telefon')),
|
serialize=False,
|
||||||
('iban', models.CharField(blank=True, max_length=34, null=True, verbose_name='IBAN')),
|
),
|
||||||
('adresse', models.TextField(blank=True, null=True, verbose_name='Adresse')),
|
),
|
||||||
('berufsgruppe', models.CharField(choices=[('student', 'Student/Studentin'), ('wissenschaftler', 'Wissenschaftler/in'), ('künstler', 'Künstler/in'), ('sozialarbeiter', 'Sozialarbeiter/in'), ('umweltschützer', 'Umweltschützer/in'), ('andere', 'Andere')], default='andere', max_length=20, verbose_name='Berufsgruppe')),
|
(
|
||||||
('ausbildungsstand', models.CharField(blank=True, max_length=100, null=True, verbose_name='Ausbildungsstand')),
|
"familienzweig",
|
||||||
('institution', models.CharField(blank=True, max_length=200, null=True, verbose_name='Institution/Organisation')),
|
models.CharField(
|
||||||
('projekt_beschreibung', models.TextField(blank=True, null=True, verbose_name='Projektbeschreibung')),
|
choices=[
|
||||||
('jaehrliches_einkommen', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, verbose_name='Jährliches Einkommen (€)')),
|
("hauptzweig", "Hauptzweig"),
|
||||||
('finanzielle_notlage', models.BooleanField(default=False, verbose_name='Finanzielle Notlage')),
|
("nebenzweig", "Nebenzweig"),
|
||||||
('notizen', models.TextField(blank=True, null=True, verbose_name='Notizen')),
|
("verwandt", "Verwandt"),
|
||||||
('aktiv', models.BooleanField(default=True, verbose_name='Aktiv')),
|
("anderer", "Anderer"),
|
||||||
|
],
|
||||||
|
default="hauptzweig",
|
||||||
|
max_length=100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("vorname", models.CharField(max_length=100, verbose_name="Vorname")),
|
||||||
|
("nachname", models.CharField(max_length=100, verbose_name="Nachname")),
|
||||||
|
(
|
||||||
|
"geburtsdatum",
|
||||||
|
models.DateField(
|
||||||
|
blank=True, null=True, verbose_name="Geburtsdatum"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"email",
|
||||||
|
models.EmailField(
|
||||||
|
blank=True, max_length=254, null=True, verbose_name="E-Mail"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"telefon",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=20, null=True, verbose_name="Telefon"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"iban",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=34, null=True, verbose_name="IBAN"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"adresse",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="Adresse"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"berufsgruppe",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("student", "Student/Studentin"),
|
||||||
|
("wissenschaftler", "Wissenschaftler/in"),
|
||||||
|
("künstler", "Künstler/in"),
|
||||||
|
("sozialarbeiter", "Sozialarbeiter/in"),
|
||||||
|
("umweltschützer", "Umweltschützer/in"),
|
||||||
|
("andere", "Andere"),
|
||||||
|
],
|
||||||
|
default="andere",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Berufsgruppe",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ausbildungsstand",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=100,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Ausbildungsstand",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"institution",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=200,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Institution/Organisation",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"projekt_beschreibung",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="Projektbeschreibung"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"jaehrliches_einkommen",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Jährliches Einkommen (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"finanzielle_notlage",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False, verbose_name="Finanzielle Notlage"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"notizen",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="Notizen"),
|
||||||
|
),
|
||||||
|
("aktiv", models.BooleanField(default=True, verbose_name="Aktiv")),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Destinatär',
|
"verbose_name": "Destinatär",
|
||||||
'verbose_name_plural': 'Destinatäre',
|
"verbose_name_plural": "Destinatäre",
|
||||||
'ordering': ['nachname', 'vorname'],
|
"ordering": ["nachname", "vorname"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Paechter',
|
name="Paechter",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('familienzweig', models.CharField(choices=[('hauptzweig', 'Hauptzweig'), ('nebenzweig', 'Nebenzweig'), ('verwandt', 'Verwandt'), ('anderer', 'Anderer')], default='hauptzweig', max_length=100)),
|
"id",
|
||||||
('vorname', models.CharField(max_length=100, verbose_name='Vorname')),
|
models.UUIDField(
|
||||||
('nachname', models.CharField(max_length=100, verbose_name='Nachname')),
|
default=uuid.uuid4,
|
||||||
('geburtsdatum', models.DateField(blank=True, null=True, verbose_name='Geburtsdatum')),
|
editable=False,
|
||||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='E-Mail')),
|
primary_key=True,
|
||||||
('telefon', models.CharField(blank=True, max_length=20, null=True, verbose_name='Telefon')),
|
serialize=False,
|
||||||
('iban', models.CharField(blank=True, max_length=34, null=True, verbose_name='IBAN')),
|
),
|
||||||
('adresse', models.TextField(blank=True, null=True, verbose_name='Adresse')),
|
),
|
||||||
('pachtnummer', models.CharField(blank=True, max_length=50, null=True, verbose_name='Pachtnummer')),
|
(
|
||||||
('pachtbeginn_erste', models.DateField(blank=True, null=True, verbose_name='Erster Pachtbeginn')),
|
"familienzweig",
|
||||||
('pachtende_letzte', models.DateField(blank=True, null=True, verbose_name='Letztes Pachtende')),
|
models.CharField(
|
||||||
('pachtzins_aktuell', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, verbose_name='Aktueller Pachtzins (€/Jahr)')),
|
choices=[
|
||||||
('landwirtschaftliche_ausbildung', models.BooleanField(default=False, verbose_name='Landwirtschaftliche Ausbildung')),
|
("hauptzweig", "Hauptzweig"),
|
||||||
('berufserfahrung_jahre', models.IntegerField(blank=True, null=True, verbose_name='Berufserfahrung (Jahre)')),
|
("nebenzweig", "Nebenzweig"),
|
||||||
('spezialisierung', models.CharField(blank=True, max_length=100, null=True, verbose_name='Spezialisierung')),
|
("verwandt", "Verwandt"),
|
||||||
('notizen', models.TextField(blank=True, null=True, verbose_name='Notizen')),
|
("anderer", "Anderer"),
|
||||||
('aktiv', models.BooleanField(default=True, verbose_name='Aktiv')),
|
],
|
||||||
|
default="hauptzweig",
|
||||||
|
max_length=100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("vorname", models.CharField(max_length=100, verbose_name="Vorname")),
|
||||||
|
("nachname", models.CharField(max_length=100, verbose_name="Nachname")),
|
||||||
|
(
|
||||||
|
"geburtsdatum",
|
||||||
|
models.DateField(
|
||||||
|
blank=True, null=True, verbose_name="Geburtsdatum"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"email",
|
||||||
|
models.EmailField(
|
||||||
|
blank=True, max_length=254, null=True, verbose_name="E-Mail"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"telefon",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=20, null=True, verbose_name="Telefon"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"iban",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=34, null=True, verbose_name="IBAN"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"adresse",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="Adresse"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pachtnummer",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=50, null=True, verbose_name="Pachtnummer"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pachtbeginn_erste",
|
||||||
|
models.DateField(
|
||||||
|
blank=True, null=True, verbose_name="Erster Pachtbeginn"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pachtende_letzte",
|
||||||
|
models.DateField(
|
||||||
|
blank=True, null=True, verbose_name="Letztes Pachtende"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pachtzins_aktuell",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Aktueller Pachtzins (€/Jahr)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"landwirtschaftliche_ausbildung",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False, verbose_name="Landwirtschaftliche Ausbildung"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"berufserfahrung_jahre",
|
||||||
|
models.IntegerField(
|
||||||
|
blank=True, null=True, verbose_name="Berufserfahrung (Jahre)"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"spezialisierung",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=100,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Spezialisierung",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"notizen",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="Notizen"),
|
||||||
|
),
|
||||||
|
("aktiv", models.BooleanField(default=True, verbose_name="Aktiv")),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Pächter',
|
"verbose_name": "Pächter",
|
||||||
'verbose_name_plural': 'Pächter',
|
"verbose_name_plural": "Pächter",
|
||||||
'ordering': ['nachname', 'vorname'],
|
"ordering": ["nachname", "vorname"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='person',
|
name="person",
|
||||||
options={'ordering': ['nachname', 'vorname'], 'verbose_name': 'Person (Legacy)', 'verbose_name_plural': 'Personen (Legacy)'},
|
options={
|
||||||
|
"ordering": ["nachname", "vorname"],
|
||||||
|
"verbose_name": "Person (Legacy)",
|
||||||
|
"verbose_name_plural": "Personen (Legacy)",
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='foerderung',
|
name="foerderung",
|
||||||
unique_together=set(),
|
unique_together=set(),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='foerderung',
|
model_name="foerderung",
|
||||||
name='person',
|
name="person",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='stiftung.person', verbose_name='Person (Legacy)'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="stiftung.person",
|
||||||
|
verbose_name="Person (Legacy)",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='foerderung',
|
model_name="foerderung",
|
||||||
name='destinataer',
|
name="destinataer",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='stiftung.destinataer', verbose_name='Destinatär'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="stiftung.destinataer",
|
||||||
|
verbose_name="Destinatär",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='verpachtung',
|
model_name="verpachtung",
|
||||||
name='paechter',
|
name="paechter",
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='stiftung.paechter', verbose_name='Pächter'),
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="stiftung.paechter",
|
||||||
|
verbose_name="Pächter",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,17 +6,27 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0005_destinataer_paechter_alter_person_options_and_more'),
|
("stiftung", "0005_destinataer_paechter_alter_person_options_and_more"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='paechter',
|
model_name="paechter",
|
||||||
name='familienzweig',
|
name="familienzweig",
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='csvimport',
|
model_name="csvimport",
|
||||||
name='import_type',
|
name="import_type",
|
||||||
field=models.CharField(choices=[('destinataere', 'Destinatäre'), ('paechter', 'Pächter'), ('laendereien', 'Ländereien'), ('verpachtungen', 'Verpachtungen'), ('personen', 'Personen (Legacy)')], max_length=20, verbose_name='Import-Typ'),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("destinataere", "Destinatäre"),
|
||||||
|
("paechter", "Pächter"),
|
||||||
|
("laendereien", "Ländereien"),
|
||||||
|
("verpachtungen", "Verpachtungen"),
|
||||||
|
("personen", "Personen (Legacy)"),
|
||||||
|
],
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Import-Typ",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,51 +6,71 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0006_remove_paechter_familienzweig_and_more'),
|
("stiftung", "0006_remove_paechter_familienzweig_and_more"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='destinataer',
|
model_name="destinataer",
|
||||||
name='adresse',
|
name="adresse",
|
||||||
),
|
),
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='paechter',
|
model_name="paechter",
|
||||||
name='adresse',
|
name="adresse",
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataer',
|
model_name="destinataer",
|
||||||
name='ort',
|
name="ort",
|
||||||
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Ort'),
|
field=models.CharField(
|
||||||
|
blank=True, max_length=100, null=True, verbose_name="Ort"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataer',
|
model_name="destinataer",
|
||||||
name='plz',
|
name="plz",
|
||||||
field=models.CharField(blank=True, max_length=10, null=True, verbose_name='PLZ'),
|
field=models.CharField(
|
||||||
|
blank=True, max_length=10, null=True, verbose_name="PLZ"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataer',
|
model_name="destinataer",
|
||||||
name='strasse',
|
name="strasse",
|
||||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Straße'),
|
field=models.CharField(
|
||||||
|
blank=True, max_length=200, null=True, verbose_name="Straße"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='paechter',
|
model_name="paechter",
|
||||||
name='ort',
|
name="ort",
|
||||||
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Ort'),
|
field=models.CharField(
|
||||||
|
blank=True, max_length=100, null=True, verbose_name="Ort"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='paechter',
|
model_name="paechter",
|
||||||
name='personentyp',
|
name="personentyp",
|
||||||
field=models.CharField(choices=[('natuerlich', 'Natürliche Person'), ('gesellschaft', 'Gesellschaft (GmbH, KG, etc.)')], default='natuerlich', max_length=20, verbose_name='Typ des Pächters'),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("natuerlich", "Natürliche Person"),
|
||||||
|
("gesellschaft", "Gesellschaft (GmbH, KG, etc.)"),
|
||||||
|
],
|
||||||
|
default="natuerlich",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Typ des Pächters",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='paechter',
|
model_name="paechter",
|
||||||
name='plz',
|
name="plz",
|
||||||
field=models.CharField(blank=True, max_length=10, null=True, verbose_name='PLZ'),
|
field=models.CharField(
|
||||||
|
blank=True, max_length=10, null=True, verbose_name="PLZ"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='paechter',
|
model_name="paechter",
|
||||||
name='strasse',
|
name="strasse",
|
||||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Straße'),
|
field=models.CharField(
|
||||||
|
blank=True, max_length=200, null=True, verbose_name="Straße"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,38 +6,57 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0007_remove_destinataer_adresse_remove_paechter_adresse_and_more'),
|
(
|
||||||
|
"stiftung",
|
||||||
|
"0007_remove_destinataer_adresse_remove_paechter_adresse_and_more",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='destinataer_id',
|
name="destinataer_id",
|
||||||
field=models.UUIDField(blank=True, null=True, verbose_name='Destinatär ID'),
|
field=models.UUIDField(blank=True, null=True, verbose_name="Destinatär ID"),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='foerderung_id',
|
name="foerderung_id",
|
||||||
field=models.UUIDField(blank=True, null=True, verbose_name='Förderung ID'),
|
field=models.UUIDField(blank=True, null=True, verbose_name="Förderung ID"),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='land_id',
|
name="land_id",
|
||||||
field=models.UUIDField(blank=True, null=True, verbose_name='Länderei ID'),
|
field=models.UUIDField(blank=True, null=True, verbose_name="Länderei ID"),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='paechter_id',
|
name="paechter_id",
|
||||||
field=models.UUIDField(blank=True, null=True, verbose_name='Pächter ID'),
|
field=models.UUIDField(blank=True, null=True, verbose_name="Pächter ID"),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='verpachtung_id',
|
name="verpachtung_id",
|
||||||
field=models.UUIDField(blank=True, null=True, verbose_name='Verpachtung ID'),
|
field=models.UUIDField(
|
||||||
|
blank=True, null=True, verbose_name="Verpachtung ID"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='kontext',
|
name="kontext",
|
||||||
field=models.CharField(choices=[('pachtvertrag', 'Pachtvertrag'), ('antrag', 'Antrag'), ('verwendungsnachweis', 'Verwendungsnachweis'), ('rechnung', 'Rechnung'), ('vertrag', 'Vertrag'), ('bericht', 'Bericht'), ('landkarte', 'Landkarte'), ('kataster', 'Kataster'), ('anderes', 'Anderes')], default='anderes', max_length=30),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("pachtvertrag", "Pachtvertrag"),
|
||||||
|
("antrag", "Antrag"),
|
||||||
|
("verwendungsnachweis", "Verwendungsnachweis"),
|
||||||
|
("rechnung", "Rechnung"),
|
||||||
|
("vertrag", "Vertrag"),
|
||||||
|
("bericht", "Bericht"),
|
||||||
|
("landkarte", "Landkarte"),
|
||||||
|
("kataster", "Kataster"),
|
||||||
|
("anderes", "Anderes"),
|
||||||
|
],
|
||||||
|
default="anderes",
|
||||||
|
max_length=30,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0008_dokumentlink_destinataer_id_and_more'),
|
("stiftung", "0008_dokumentlink_destinataer_id_and_more"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='paperless_document_id',
|
name="paperless_document_id",
|
||||||
field=models.IntegerField(),
|
field=models.IntegerField(),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,100 +1,344 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-08-24 17:48
|
# Generated by Django 5.0.6 on 2025-08-24 17:48
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0009_alter_dokumentlink_paperless_document_id'),
|
("stiftung", "0009_alter_dokumentlink_paperless_document_id"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Rentmeister',
|
name="Rentmeister",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('anrede', models.CharField(blank=True, choices=[('herr', 'Herr'), ('frau', 'Frau'), ('dr', 'Dr.'), ('prof', 'Prof.'), ('prof_dr', 'Prof. Dr.')], max_length=10, verbose_name='Anrede')),
|
"id",
|
||||||
('vorname', models.CharField(max_length=100, verbose_name='Vorname')),
|
models.UUIDField(
|
||||||
('nachname', models.CharField(max_length=100, verbose_name='Nachname')),
|
default=uuid.uuid4,
|
||||||
('titel', models.CharField(blank=True, max_length=50, verbose_name='Titel')),
|
editable=False,
|
||||||
('email', models.EmailField(blank=True, max_length=254, verbose_name='E-Mail')),
|
primary_key=True,
|
||||||
('telefon', models.CharField(blank=True, max_length=20, verbose_name='Telefon')),
|
serialize=False,
|
||||||
('mobil', models.CharField(blank=True, max_length=20, verbose_name='Mobil')),
|
),
|
||||||
('strasse', models.CharField(blank=True, max_length=200, verbose_name='Straße')),
|
),
|
||||||
('plz', models.CharField(blank=True, max_length=10, verbose_name='PLZ')),
|
(
|
||||||
('ort', models.CharField(blank=True, max_length=100, verbose_name='Ort')),
|
"anrede",
|
||||||
('iban', models.CharField(blank=True, max_length=34, verbose_name='IBAN')),
|
models.CharField(
|
||||||
('bic', models.CharField(blank=True, max_length=11, verbose_name='BIC')),
|
blank=True,
|
||||||
('bank_name', models.CharField(blank=True, max_length=100, verbose_name='Bank')),
|
choices=[
|
||||||
('seit_datum', models.DateField(verbose_name='Rentmeister seit')),
|
("herr", "Herr"),
|
||||||
('bis_datum', models.DateField(blank=True, null=True, verbose_name='Rentmeister bis')),
|
("frau", "Frau"),
|
||||||
('aktiv', models.BooleanField(default=True, verbose_name='Aktiv')),
|
("dr", "Dr."),
|
||||||
('monatliche_verguetung', models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True, verbose_name='Monatliche Vergütung (€)')),
|
("prof", "Prof."),
|
||||||
('km_pauschale', models.DecimalField(decimal_places=2, default=0.3, max_digits=4, verbose_name='Kilometerpauschale (€/km)')),
|
("prof_dr", "Prof. Dr."),
|
||||||
('notizen', models.TextField(blank=True, verbose_name='Notizen')),
|
],
|
||||||
('erstellt_am', models.DateTimeField(auto_now_add=True)),
|
max_length=10,
|
||||||
('aktualisiert_am', models.DateTimeField(auto_now=True)),
|
verbose_name="Anrede",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("vorname", models.CharField(max_length=100, verbose_name="Vorname")),
|
||||||
|
("nachname", models.CharField(max_length=100, verbose_name="Nachname")),
|
||||||
|
(
|
||||||
|
"titel",
|
||||||
|
models.CharField(blank=True, max_length=50, verbose_name="Titel"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"email",
|
||||||
|
models.EmailField(
|
||||||
|
blank=True, max_length=254, verbose_name="E-Mail"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"telefon",
|
||||||
|
models.CharField(blank=True, max_length=20, verbose_name="Telefon"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"mobil",
|
||||||
|
models.CharField(blank=True, max_length=20, verbose_name="Mobil"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"strasse",
|
||||||
|
models.CharField(blank=True, max_length=200, verbose_name="Straße"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"plz",
|
||||||
|
models.CharField(blank=True, max_length=10, verbose_name="PLZ"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ort",
|
||||||
|
models.CharField(blank=True, max_length=100, verbose_name="Ort"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"iban",
|
||||||
|
models.CharField(blank=True, max_length=34, verbose_name="IBAN"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"bic",
|
||||||
|
models.CharField(blank=True, max_length=11, verbose_name="BIC"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"bank_name",
|
||||||
|
models.CharField(blank=True, max_length=100, verbose_name="Bank"),
|
||||||
|
),
|
||||||
|
("seit_datum", models.DateField(verbose_name="Rentmeister seit")),
|
||||||
|
(
|
||||||
|
"bis_datum",
|
||||||
|
models.DateField(
|
||||||
|
blank=True, null=True, verbose_name="Rentmeister bis"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("aktiv", models.BooleanField(default=True, verbose_name="Aktiv")),
|
||||||
|
(
|
||||||
|
"monatliche_verguetung",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=8,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Monatliche Vergütung (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"km_pauschale",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0.3,
|
||||||
|
max_digits=4,
|
||||||
|
verbose_name="Kilometerpauschale (€/km)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("notizen", models.TextField(blank=True, verbose_name="Notizen")),
|
||||||
|
("erstellt_am", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("aktualisiert_am", models.DateTimeField(auto_now=True)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Rentmeister',
|
"verbose_name": "Rentmeister",
|
||||||
'verbose_name_plural': 'Rentmeister',
|
"verbose_name_plural": "Rentmeister",
|
||||||
'ordering': ['nachname', 'vorname'],
|
"ordering": ["nachname", "vorname"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='StiftungsKonto',
|
name="StiftungsKonto",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('kontoname', models.CharField(max_length=200, verbose_name='Kontoname')),
|
"id",
|
||||||
('bank_name', models.CharField(max_length=200, verbose_name='Bank')),
|
models.UUIDField(
|
||||||
('iban', models.CharField(max_length=34, verbose_name='IBAN')),
|
default=uuid.uuid4,
|
||||||
('bic', models.CharField(blank=True, max_length=11, verbose_name='BIC')),
|
editable=False,
|
||||||
('konto_typ', models.CharField(choices=[('girokonto', 'Girokonto'), ('sparkonto', 'Sparkonto'), ('festgeld', 'Festgeld'), ('tagesgeld', 'Tagesgeld'), ('depot', 'Depot'), ('sonstiges', 'Sonstiges')], default='girokonto', max_length=20, verbose_name='Kontotyp')),
|
primary_key=True,
|
||||||
('saldo', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='Aktueller Saldo')),
|
serialize=False,
|
||||||
('saldo_datum', models.DateField(blank=True, null=True, verbose_name='Saldo-Datum')),
|
),
|
||||||
('zinssatz', models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True, verbose_name='Zinssatz (%)')),
|
),
|
||||||
('laufzeit_bis', models.DateField(blank=True, null=True, verbose_name='Laufzeit bis')),
|
(
|
||||||
('aktiv', models.BooleanField(default=True, verbose_name='Aktiv')),
|
"kontoname",
|
||||||
('notizen', models.TextField(blank=True, verbose_name='Notizen')),
|
models.CharField(max_length=200, verbose_name="Kontoname"),
|
||||||
('erstellt_am', models.DateTimeField(auto_now_add=True)),
|
),
|
||||||
('aktualisiert_am', models.DateTimeField(auto_now=True)),
|
("bank_name", models.CharField(max_length=200, verbose_name="Bank")),
|
||||||
|
("iban", models.CharField(max_length=34, verbose_name="IBAN")),
|
||||||
|
(
|
||||||
|
"bic",
|
||||||
|
models.CharField(blank=True, max_length=11, verbose_name="BIC"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"konto_typ",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("girokonto", "Girokonto"),
|
||||||
|
("sparkonto", "Sparkonto"),
|
||||||
|
("festgeld", "Festgeld"),
|
||||||
|
("tagesgeld", "Tagesgeld"),
|
||||||
|
("depot", "Depot"),
|
||||||
|
("sonstiges", "Sonstiges"),
|
||||||
|
],
|
||||||
|
default="girokonto",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Kontotyp",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"saldo",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0.0,
|
||||||
|
max_digits=10,
|
||||||
|
verbose_name="Aktueller Saldo",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"saldo_datum",
|
||||||
|
models.DateField(blank=True, null=True, verbose_name="Saldo-Datum"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zinssatz",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=5,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Zinssatz (%)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"laufzeit_bis",
|
||||||
|
models.DateField(
|
||||||
|
blank=True, null=True, verbose_name="Laufzeit bis"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("aktiv", models.BooleanField(default=True, verbose_name="Aktiv")),
|
||||||
|
("notizen", models.TextField(blank=True, verbose_name="Notizen")),
|
||||||
|
("erstellt_am", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("aktualisiert_am", models.DateTimeField(auto_now=True)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Stiftungskonto',
|
"verbose_name": "Stiftungskonto",
|
||||||
'verbose_name_plural': 'Stiftungskonten',
|
"verbose_name_plural": "Stiftungskonten",
|
||||||
'ordering': ['bank_name', 'kontoname'],
|
"ordering": ["bank_name", "kontoname"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Verwaltungskosten',
|
name="Verwaltungskosten",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('bezeichnung', models.CharField(max_length=200, verbose_name='Bezeichnung')),
|
"id",
|
||||||
('kategorie', models.CharField(choices=[('rechnung_intern', 'Interne Rechnung'), ('bueroausstattung', 'Büroausstattung'), ('fahrtkosten', 'Fahrtkosten'), ('porto', 'Porto & Versand'), ('telefon_internet', 'Telefon & Internet'), ('software', 'Software & Lizenzen'), ('beratung', 'Beratung & Dienstleistungen'), ('versicherung', 'Versicherungen'), ('steuerberatung', 'Steuerberatung'), ('bankgebuehren', 'Bankgebühren'), ('sonstiges', 'Sonstiges')], max_length=30, verbose_name='Kategorie')),
|
models.UUIDField(
|
||||||
('betrag', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Betrag (€)')),
|
default=uuid.uuid4,
|
||||||
('datum', models.DateField(verbose_name='Datum')),
|
editable=False,
|
||||||
('lieferant_firma', models.CharField(blank=True, max_length=200, verbose_name='Lieferant/Firma')),
|
primary_key=True,
|
||||||
('rechnungsnummer', models.CharField(blank=True, max_length=100, verbose_name='Rechnungsnummer')),
|
serialize=False,
|
||||||
('status', models.CharField(choices=[('geplant', 'Geplant'), ('bestellt', 'Bestellt'), ('erhalten', 'Erhalten'), ('bezahlt', 'Bezahlt'), ('storniert', 'Storniert')], default='geplant', max_length=20, verbose_name='Status')),
|
),
|
||||||
('km_anzahl', models.DecimalField(blank=True, decimal_places=1, max_digits=8, null=True, verbose_name='Kilometer')),
|
),
|
||||||
('km_satz', models.DecimalField(blank=True, decimal_places=2, max_digits=4, null=True, verbose_name='€/km')),
|
(
|
||||||
('von_ort', models.CharField(blank=True, max_length=100, verbose_name='Von (Ort)')),
|
"bezeichnung",
|
||||||
('nach_ort', models.CharField(blank=True, max_length=100, verbose_name='Nach (Ort)')),
|
models.CharField(max_length=200, verbose_name="Bezeichnung"),
|
||||||
('zweck', models.CharField(blank=True, max_length=200, verbose_name='Zweck der Fahrt')),
|
),
|
||||||
('beschreibung', models.TextField(blank=True, verbose_name='Beschreibung')),
|
(
|
||||||
('notizen', models.TextField(blank=True, verbose_name='Notizen')),
|
"kategorie",
|
||||||
('erstellt_am', models.DateTimeField(auto_now_add=True)),
|
models.CharField(
|
||||||
('aktualisiert_am', models.DateTimeField(auto_now=True)),
|
choices=[
|
||||||
('konto', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.stiftungskonto', verbose_name='Konto')),
|
("rechnung_intern", "Interne Rechnung"),
|
||||||
('rentmeister', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.rentmeister', verbose_name='Rentmeister')),
|
("bueroausstattung", "Büroausstattung"),
|
||||||
|
("fahrtkosten", "Fahrtkosten"),
|
||||||
|
("porto", "Porto & Versand"),
|
||||||
|
("telefon_internet", "Telefon & Internet"),
|
||||||
|
("software", "Software & Lizenzen"),
|
||||||
|
("beratung", "Beratung & Dienstleistungen"),
|
||||||
|
("versicherung", "Versicherungen"),
|
||||||
|
("steuerberatung", "Steuerberatung"),
|
||||||
|
("bankgebuehren", "Bankgebühren"),
|
||||||
|
("sonstiges", "Sonstiges"),
|
||||||
|
],
|
||||||
|
max_length=30,
|
||||||
|
verbose_name="Kategorie",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"betrag",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2, max_digits=10, verbose_name="Betrag (€)"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("datum", models.DateField(verbose_name="Datum")),
|
||||||
|
(
|
||||||
|
"lieferant_firma",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=200, verbose_name="Lieferant/Firma"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"rechnungsnummer",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=100, verbose_name="Rechnungsnummer"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("geplant", "Geplant"),
|
||||||
|
("bestellt", "Bestellt"),
|
||||||
|
("erhalten", "Erhalten"),
|
||||||
|
("bezahlt", "Bezahlt"),
|
||||||
|
("storniert", "Storniert"),
|
||||||
|
],
|
||||||
|
default="geplant",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"km_anzahl",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=1,
|
||||||
|
max_digits=8,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Kilometer",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"km_satz",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=4,
|
||||||
|
null=True,
|
||||||
|
verbose_name="€/km",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"von_ort",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=100, verbose_name="Von (Ort)"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"nach_ort",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=100, verbose_name="Nach (Ort)"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zweck",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=200, verbose_name="Zweck der Fahrt"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"beschreibung",
|
||||||
|
models.TextField(blank=True, verbose_name="Beschreibung"),
|
||||||
|
),
|
||||||
|
("notizen", models.TextField(blank=True, verbose_name="Notizen")),
|
||||||
|
("erstellt_am", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("aktualisiert_am", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"konto",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="stiftung.stiftungskonto",
|
||||||
|
verbose_name="Konto",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"rentmeister",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="stiftung.rentmeister",
|
||||||
|
verbose_name="Rentmeister",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Verwaltungskosten',
|
"verbose_name": "Verwaltungskosten",
|
||||||
'verbose_name_plural': 'Verwaltungskosten',
|
"verbose_name_plural": "Verwaltungskosten",
|
||||||
'ordering': ['-datum', '-erstellt_am'],
|
"ordering": ["-datum", "-erstellt_am"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,44 +1,156 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-08-24 19:27
|
# Generated by Django 5.0.6 on 2025-08-24 19:27
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0010_rentmeister_stiftungskonto_verwaltungskosten'),
|
("stiftung", "0010_rentmeister_stiftungskonto_verwaltungskosten"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='BankTransaction',
|
name="BankTransaction",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('datum', models.DateField(verbose_name='Buchungsdatum')),
|
"id",
|
||||||
('valuta', models.DateField(blank=True, null=True, verbose_name='Valutadatum')),
|
models.UUIDField(
|
||||||
('betrag', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Betrag (€)')),
|
default=uuid.uuid4,
|
||||||
('waehrung', models.CharField(default='EUR', max_length=3, verbose_name='Währung')),
|
editable=False,
|
||||||
('verwendungszweck', models.TextField(verbose_name='Verwendungszweck')),
|
primary_key=True,
|
||||||
('empfaenger_zahlungspflichtiger', models.CharField(blank=True, max_length=200, verbose_name='Empfänger/Zahlungspflichtiger')),
|
serialize=False,
|
||||||
('iban_gegenpartei', models.CharField(blank=True, max_length=34, verbose_name='IBAN Gegenpartei')),
|
),
|
||||||
('bic_gegenpartei', models.CharField(blank=True, max_length=11, verbose_name='BIC Gegenpartei')),
|
),
|
||||||
('referenz', models.CharField(blank=True, max_length=100, verbose_name='Referenz/Transaktions-ID')),
|
("datum", models.DateField(verbose_name="Buchungsdatum")),
|
||||||
('transaction_type', models.CharField(choices=[('eingang', 'Eingang'), ('ausgang', 'Ausgang'), ('lastschrift', 'Lastschrift'), ('ueberweisung', 'Überweisung'), ('dauerauftrag', 'Dauerauftrag'), ('kartenzahlung', 'Kartenzahlung'), ('zinsen', 'Zinsen'), ('gebuehren', 'Gebühren'), ('sonstiges', 'Sonstiges')], default='sonstiges', max_length=20, verbose_name='Transaktionsart')),
|
(
|
||||||
('status', models.CharField(choices=[('imported', 'Importiert'), ('verified', 'Geprüft'), ('assigned', 'Zugeordnet'), ('ignored', 'Ignoriert')], default='imported', max_length=20, verbose_name='Status')),
|
"valuta",
|
||||||
('kommentare', models.TextField(blank=True, verbose_name='Kommentare')),
|
models.DateField(blank=True, null=True, verbose_name="Valutadatum"),
|
||||||
('import_datei', models.CharField(blank=True, max_length=255, verbose_name='Import-Datei')),
|
),
|
||||||
('importiert_am', models.DateTimeField(auto_now_add=True, verbose_name='Importiert am')),
|
(
|
||||||
('saldo_nach_buchung', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, verbose_name='Saldo nach Buchung')),
|
"betrag",
|
||||||
('konto', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='stiftung.stiftungskonto', verbose_name='Konto')),
|
models.DecimalField(
|
||||||
('verwaltungskosten', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.verwaltungskosten', verbose_name='Zugeordnete Verwaltungskosten')),
|
decimal_places=2, max_digits=12, verbose_name="Betrag (€)"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"waehrung",
|
||||||
|
models.CharField(
|
||||||
|
default="EUR", max_length=3, verbose_name="Währung"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("verwendungszweck", models.TextField(verbose_name="Verwendungszweck")),
|
||||||
|
(
|
||||||
|
"empfaenger_zahlungspflichtiger",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=200,
|
||||||
|
verbose_name="Empfänger/Zahlungspflichtiger",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"iban_gegenpartei",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=34, verbose_name="IBAN Gegenpartei"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"bic_gegenpartei",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=11, verbose_name="BIC Gegenpartei"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"referenz",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=100,
|
||||||
|
verbose_name="Referenz/Transaktions-ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"transaction_type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("eingang", "Eingang"),
|
||||||
|
("ausgang", "Ausgang"),
|
||||||
|
("lastschrift", "Lastschrift"),
|
||||||
|
("ueberweisung", "Überweisung"),
|
||||||
|
("dauerauftrag", "Dauerauftrag"),
|
||||||
|
("kartenzahlung", "Kartenzahlung"),
|
||||||
|
("zinsen", "Zinsen"),
|
||||||
|
("gebuehren", "Gebühren"),
|
||||||
|
("sonstiges", "Sonstiges"),
|
||||||
|
],
|
||||||
|
default="sonstiges",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Transaktionsart",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("imported", "Importiert"),
|
||||||
|
("verified", "Geprüft"),
|
||||||
|
("assigned", "Zugeordnet"),
|
||||||
|
("ignored", "Ignoriert"),
|
||||||
|
],
|
||||||
|
default="imported",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("kommentare", models.TextField(blank=True, verbose_name="Kommentare")),
|
||||||
|
(
|
||||||
|
"import_datei",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=255, verbose_name="Import-Datei"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"importiert_am",
|
||||||
|
models.DateTimeField(
|
||||||
|
auto_now_add=True, verbose_name="Importiert am"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"saldo_nach_buchung",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Saldo nach Buchung",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"konto",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="stiftung.stiftungskonto",
|
||||||
|
verbose_name="Konto",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"verwaltungskosten",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="stiftung.verwaltungskosten",
|
||||||
|
verbose_name="Zugeordnete Verwaltungskosten",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Banktransaktion',
|
"verbose_name": "Banktransaktion",
|
||||||
'verbose_name_plural': 'Banktransaktionen',
|
"verbose_name_plural": "Banktransaktionen",
|
||||||
'ordering': ['-datum', '-importiert_am'],
|
"ordering": ["-datum", "-importiert_am"],
|
||||||
'unique_together': {('konto', 'datum', 'betrag', 'referenz')},
|
"unique_together": {("konto", "datum", "betrag", "referenz")},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,28 +7,55 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0011_banktransaction'),
|
("stiftung", "0011_banktransaction"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='verwaltungskosten',
|
model_name="verwaltungskosten",
|
||||||
name='quellkonto',
|
name="quellkonto",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ausgaben', to='stiftung.stiftungskonto', verbose_name='Quellkonto'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="ausgaben",
|
||||||
|
to="stiftung.stiftungskonto",
|
||||||
|
verbose_name="Quellkonto",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='verwaltungskosten',
|
model_name="verwaltungskosten",
|
||||||
name='zahlungskonto',
|
name="zahlungskonto",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='zahlungen', to='stiftung.stiftungskonto', verbose_name='Zahlungskonto'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="zahlungen",
|
||||||
|
to="stiftung.stiftungskonto",
|
||||||
|
verbose_name="Zahlungskonto",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='verwaltungskosten',
|
model_name="verwaltungskosten",
|
||||||
name='konto',
|
name="konto",
|
||||||
field=models.ForeignKey(blank=True, help_text='Veraltet - verwende Zahlungskonto und Quellkonto', null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.stiftungskonto', verbose_name='Konto (Legacy)'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
help_text="Veraltet - verwende Zahlungskonto und Quellkonto",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="stiftung.stiftungskonto",
|
||||||
|
verbose_name="Konto (Legacy)",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='verwaltungskosten',
|
model_name="verwaltungskosten",
|
||||||
name='rentmeister',
|
name="rentmeister",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.rentmeister', verbose_name='Zuständiger Rentmeister'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="stiftung.rentmeister",
|
||||||
|
verbose_name="Zuständiger Rentmeister",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,13 +6,25 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0012_verwaltungskosten_quellkonto_and_more'),
|
("stiftung", "0012_verwaltungskosten_quellkonto_and_more"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='verwaltungskosten',
|
model_name="verwaltungskosten",
|
||||||
name='status',
|
name="status",
|
||||||
field=models.CharField(choices=[('geplant', 'Geplant'), ('bestellt', 'Bestellt'), ('erhalten', 'Erhalten'), ('in_bearbeitung', 'In Bearbeitung'), ('bezahlt', 'Bezahlt'), ('storniert', 'Storniert')], default='geplant', max_length=20, verbose_name='Status'),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("geplant", "Geplant"),
|
||||||
|
("bestellt", "Bestellt"),
|
||||||
|
("erhalten", "Erhalten"),
|
||||||
|
("in_bearbeitung", "In Bearbeitung"),
|
||||||
|
("bezahlt", "Bezahlt"),
|
||||||
|
("storniert", "Storniert"),
|
||||||
|
],
|
||||||
|
default="geplant",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Status",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0013_alter_verwaltungskosten_status'),
|
("stiftung", "0013_alter_verwaltungskosten_status"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='rentmeister_id',
|
name="rentmeister_id",
|
||||||
field=models.UUIDField(blank=True, null=True, verbose_name='Rentmeister ID'),
|
field=models.UUIDField(
|
||||||
|
blank=True, null=True, verbose_name="Rentmeister ID"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-08-26 08:33
|
# Generated by Django 5.0.6 on 2025-08-26 08:33
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
@@ -9,55 +10,229 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0014_dokumentlink_rentmeister_id'),
|
("stiftung", "0014_dokumentlink_rentmeister_id"),
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='BackupJob',
|
name="BackupJob",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('backup_type', models.CharField(choices=[('full', 'Vollständiges Backup'), ('database', 'Nur Datenbank'), ('files', 'Nur Dateien')], max_length=20, verbose_name='Backup-Typ')),
|
"id",
|
||||||
('status', models.CharField(choices=[('pending', 'Wartend'), ('running', 'Läuft'), ('completed', 'Abgeschlossen'), ('failed', 'Fehlgeschlagen')], default='pending', max_length=20, verbose_name='Status')),
|
models.UUIDField(
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am')),
|
default=uuid.uuid4,
|
||||||
('started_at', models.DateTimeField(blank=True, null=True, verbose_name='Gestartet am')),
|
editable=False,
|
||||||
('completed_at', models.DateTimeField(blank=True, null=True, verbose_name='Abgeschlossen am')),
|
primary_key=True,
|
||||||
('backup_filename', models.CharField(blank=True, max_length=255, verbose_name='Backup-Dateiname')),
|
serialize=False,
|
||||||
('backup_size', models.BigIntegerField(blank=True, null=True, verbose_name='Backup-Größe (Bytes)')),
|
),
|
||||||
('error_message', models.TextField(blank=True, verbose_name='Fehlermeldung')),
|
),
|
||||||
('database_size', models.BigIntegerField(blank=True, null=True, verbose_name='Datenbankgröße (Bytes)')),
|
(
|
||||||
('files_count', models.IntegerField(blank=True, null=True, verbose_name='Anzahl Dateien')),
|
"backup_type",
|
||||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Erstellt von')),
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("full", "Vollständiges Backup"),
|
||||||
|
("database", "Nur Datenbank"),
|
||||||
|
("files", "Nur Dateien"),
|
||||||
|
],
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Backup-Typ",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("pending", "Wartend"),
|
||||||
|
("running", "Läuft"),
|
||||||
|
("completed", "Abgeschlossen"),
|
||||||
|
("failed", "Fehlgeschlagen"),
|
||||||
|
],
|
||||||
|
default="pending",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created_at",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="Erstellt am"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"started_at",
|
||||||
|
models.DateTimeField(
|
||||||
|
blank=True, null=True, verbose_name="Gestartet am"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"completed_at",
|
||||||
|
models.DateTimeField(
|
||||||
|
blank=True, null=True, verbose_name="Abgeschlossen am"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"backup_filename",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=255, verbose_name="Backup-Dateiname"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"backup_size",
|
||||||
|
models.BigIntegerField(
|
||||||
|
blank=True, null=True, verbose_name="Backup-Größe (Bytes)"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"error_message",
|
||||||
|
models.TextField(blank=True, verbose_name="Fehlermeldung"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"database_size",
|
||||||
|
models.BigIntegerField(
|
||||||
|
blank=True, null=True, verbose_name="Datenbankgröße (Bytes)"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"files_count",
|
||||||
|
models.IntegerField(
|
||||||
|
blank=True, null=True, verbose_name="Anzahl Dateien"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created_by",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="Erstellt von",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Backup-Job',
|
"verbose_name": "Backup-Job",
|
||||||
'verbose_name_plural': 'Backup-Jobs',
|
"verbose_name_plural": "Backup-Jobs",
|
||||||
'ordering': ['-created_at'],
|
"ordering": ["-created_at"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='AuditLog',
|
name="AuditLog",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('username', models.CharField(max_length=150, verbose_name='Benutzername')),
|
"id",
|
||||||
('timestamp', models.DateTimeField(auto_now_add=True, verbose_name='Zeitpunkt')),
|
models.UUIDField(
|
||||||
('action', models.CharField(choices=[('create', 'Erstellt'), ('update', 'Aktualisiert'), ('delete', 'Gelöscht'), ('link', 'Verknüpft'), ('unlink', 'Verknüpfung entfernt'), ('login', 'Anmeldung'), ('logout', 'Abmeldung'), ('backup', 'Backup erstellt'), ('restore', 'Wiederherstellung'), ('export', 'Export'), ('import', 'Import')], max_length=20, verbose_name='Aktion')),
|
default=uuid.uuid4,
|
||||||
('entity_type', models.CharField(choices=[('destinataer', 'Destinatär'), ('land', 'Länderei'), ('paechter', 'Pächter'), ('verpachtung', 'Verpachtung'), ('foerderung', 'Förderung'), ('rentmeister', 'Rentmeister'), ('stiftungskonto', 'Stiftungskonto'), ('verwaltungskosten', 'Verwaltungskosten'), ('banktransaction', 'Bank-Transaktion'), ('dokumentlink', 'Dokument-Verknüpfung'), ('system', 'System'), ('user', 'Benutzer')], max_length=20, verbose_name='Entitätstyp')),
|
editable=False,
|
||||||
('entity_id', models.CharField(blank=True, max_length=100, verbose_name='Entitäts-ID')),
|
primary_key=True,
|
||||||
('entity_name', models.CharField(max_length=255, verbose_name='Entitätsname')),
|
serialize=False,
|
||||||
('description', models.TextField(verbose_name='Beschreibung')),
|
),
|
||||||
('changes', models.JSONField(blank=True, null=True, verbose_name='Änderungen')),
|
),
|
||||||
('ip_address', models.GenericIPAddressField(blank=True, null=True, verbose_name='IP-Adresse')),
|
(
|
||||||
('user_agent', models.TextField(blank=True, verbose_name='User Agent')),
|
"username",
|
||||||
('session_key', models.CharField(blank=True, max_length=40, verbose_name='Session-Key')),
|
models.CharField(max_length=150, verbose_name="Benutzername"),
|
||||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Benutzer')),
|
),
|
||||||
|
(
|
||||||
|
"timestamp",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="Zeitpunkt"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"action",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("create", "Erstellt"),
|
||||||
|
("update", "Aktualisiert"),
|
||||||
|
("delete", "Gelöscht"),
|
||||||
|
("link", "Verknüpft"),
|
||||||
|
("unlink", "Verknüpfung entfernt"),
|
||||||
|
("login", "Anmeldung"),
|
||||||
|
("logout", "Abmeldung"),
|
||||||
|
("backup", "Backup erstellt"),
|
||||||
|
("restore", "Wiederherstellung"),
|
||||||
|
("export", "Export"),
|
||||||
|
("import", "Import"),
|
||||||
|
],
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Aktion",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"entity_type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("destinataer", "Destinatär"),
|
||||||
|
("land", "Länderei"),
|
||||||
|
("paechter", "Pächter"),
|
||||||
|
("verpachtung", "Verpachtung"),
|
||||||
|
("foerderung", "Förderung"),
|
||||||
|
("rentmeister", "Rentmeister"),
|
||||||
|
("stiftungskonto", "Stiftungskonto"),
|
||||||
|
("verwaltungskosten", "Verwaltungskosten"),
|
||||||
|
("banktransaction", "Bank-Transaktion"),
|
||||||
|
("dokumentlink", "Dokument-Verknüpfung"),
|
||||||
|
("system", "System"),
|
||||||
|
("user", "Benutzer"),
|
||||||
|
],
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Entitätstyp",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"entity_id",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=100, verbose_name="Entitäts-ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"entity_name",
|
||||||
|
models.CharField(max_length=255, verbose_name="Entitätsname"),
|
||||||
|
),
|
||||||
|
("description", models.TextField(verbose_name="Beschreibung")),
|
||||||
|
(
|
||||||
|
"changes",
|
||||||
|
models.JSONField(blank=True, null=True, verbose_name="Änderungen"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ip_address",
|
||||||
|
models.GenericIPAddressField(
|
||||||
|
blank=True, null=True, verbose_name="IP-Adresse"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("user_agent", models.TextField(blank=True, verbose_name="User Agent")),
|
||||||
|
(
|
||||||
|
"session_key",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=40, verbose_name="Session-Key"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="Benutzer",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Audit Log Eintrag',
|
"verbose_name": "Audit Log Eintrag",
|
||||||
'verbose_name_plural': 'Audit Log Einträge',
|
"verbose_name_plural": "Audit Log Einträge",
|
||||||
'ordering': ['-timestamp'],
|
"ordering": ["-timestamp"],
|
||||||
'indexes': [models.Index(fields=['timestamp'], name='stiftung_au_timesta_c4591e_idx'), models.Index(fields=['user', 'timestamp'], name='stiftung_au_user_id_e3fc12_idx'), models.Index(fields=['entity_type', 'timestamp'], name='stiftung_au_entity__68f25d_idx'), models.Index(fields=['action', 'timestamp'], name='stiftung_au_action_288765_idx')],
|
"indexes": [
|
||||||
|
models.Index(
|
||||||
|
fields=["timestamp"], name="stiftung_au_timesta_c4591e_idx"
|
||||||
|
),
|
||||||
|
models.Index(
|
||||||
|
fields=["user", "timestamp"],
|
||||||
|
name="stiftung_au_user_id_e3fc12_idx",
|
||||||
|
),
|
||||||
|
models.Index(
|
||||||
|
fields=["entity_type", "timestamp"],
|
||||||
|
name="stiftung_au_entity__68f25d_idx",
|
||||||
|
),
|
||||||
|
models.Index(
|
||||||
|
fields=["action", "timestamp"],
|
||||||
|
name="stiftung_au_action_288765_idx",
|
||||||
|
),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,19 +6,57 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0015_backupjob_auditlog'),
|
("stiftung", "0015_backupjob_auditlog"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ApplicationPermission',
|
name="ApplicationPermission",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'permissions': [('manage_destinataere', 'Kann Destinatäre verwalten'), ('view_destinataere', 'Kann Destinatäre anzeigen'), ('manage_land', 'Kann Ländereien verwalten'), ('view_land', 'Kann Ländereien anzeigen'), ('manage_paechter', 'Kann Pächter verwalten'), ('view_paechter', 'Kann Pächter anzeigen'), ('manage_verpachtungen', 'Kann Verpachtungen verwalten'), ('view_verpachtungen', 'Kann Verpachtungen anzeigen'), ('manage_foerderungen', 'Kann Förderungen verwalten'), ('view_foerderungen', 'Kann Förderungen anzeigen'), ('manage_documents', 'Kann Dokumente verwalten'), ('view_documents', 'Kann Dokumente anzeigen'), ('link_documents', 'Kann Dokumente verknüpfen'), ('manage_verwaltungskosten', 'Kann Verwaltungskosten verwalten'), ('view_verwaltungskosten', 'Kann Verwaltungskosten anzeigen'), ('approve_payments', 'Kann Zahlungen genehmigen'), ('manage_konten', 'Kann Stiftungskonten verwalten'), ('view_konten', 'Kann Stiftungskonten anzeigen'), ('manage_rentmeister', 'Kann Rentmeister verwalten'), ('view_rentmeister', 'Kann Rentmeister anzeigen'), ('access_administration', 'Kann Administration aufrufen'), ('view_audit_logs', 'Kann Audit-Logs anzeigen'), ('manage_backups', 'Kann Backups erstellen und verwalten'), ('manage_users', 'Kann Benutzer verwalten'), ('manage_permissions', 'Kann Berechtigungen verwalten'), ('import_data', 'Kann Daten importieren'), ('export_data', 'Kann Daten exportieren'), ('access_django_admin', 'Kann Django Admin aufrufen'), ('view_system_stats', 'Kann Systemstatistiken anzeigen')],
|
"permissions": [
|
||||||
'managed': False,
|
("manage_destinataere", "Kann Destinatäre verwalten"),
|
||||||
'default_permissions': (),
|
("view_destinataere", "Kann Destinatäre anzeigen"),
|
||||||
|
("manage_land", "Kann Ländereien verwalten"),
|
||||||
|
("view_land", "Kann Ländereien anzeigen"),
|
||||||
|
("manage_paechter", "Kann Pächter verwalten"),
|
||||||
|
("view_paechter", "Kann Pächter anzeigen"),
|
||||||
|
("manage_verpachtungen", "Kann Verpachtungen verwalten"),
|
||||||
|
("view_verpachtungen", "Kann Verpachtungen anzeigen"),
|
||||||
|
("manage_foerderungen", "Kann Förderungen verwalten"),
|
||||||
|
("view_foerderungen", "Kann Förderungen anzeigen"),
|
||||||
|
("manage_documents", "Kann Dokumente verwalten"),
|
||||||
|
("view_documents", "Kann Dokumente anzeigen"),
|
||||||
|
("link_documents", "Kann Dokumente verknüpfen"),
|
||||||
|
("manage_verwaltungskosten", "Kann Verwaltungskosten verwalten"),
|
||||||
|
("view_verwaltungskosten", "Kann Verwaltungskosten anzeigen"),
|
||||||
|
("approve_payments", "Kann Zahlungen genehmigen"),
|
||||||
|
("manage_konten", "Kann Stiftungskonten verwalten"),
|
||||||
|
("view_konten", "Kann Stiftungskonten anzeigen"),
|
||||||
|
("manage_rentmeister", "Kann Rentmeister verwalten"),
|
||||||
|
("view_rentmeister", "Kann Rentmeister anzeigen"),
|
||||||
|
("access_administration", "Kann Administration aufrufen"),
|
||||||
|
("view_audit_logs", "Kann Audit-Logs anzeigen"),
|
||||||
|
("manage_backups", "Kann Backups erstellen und verwalten"),
|
||||||
|
("manage_users", "Kann Benutzer verwalten"),
|
||||||
|
("manage_permissions", "Kann Berechtigungen verwalten"),
|
||||||
|
("import_data", "Kann Daten importieren"),
|
||||||
|
("export_data", "Kann Daten exportieren"),
|
||||||
|
("access_django_admin", "Kann Django Admin aufrufen"),
|
||||||
|
("view_system_stats", "Kann Systemstatistiken anzeigen"),
|
||||||
|
],
|
||||||
|
"managed": False,
|
||||||
|
"default_permissions": (),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,48 +7,74 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0016_applicationpermission'),
|
("stiftung", "0016_applicationpermission"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataer',
|
model_name="destinataer",
|
||||||
name='haushaltsgroesse',
|
name="haushaltsgroesse",
|
||||||
field=models.PositiveIntegerField(default=1, verbose_name='Haushaltsgröße'),
|
field=models.PositiveIntegerField(default=1, verbose_name="Haushaltsgröße"),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataer',
|
model_name="destinataer",
|
||||||
name='ist_abkoemmling',
|
name="ist_abkoemmling",
|
||||||
field=models.BooleanField(default=False, verbose_name='Abkömmling gem. Satzung'),
|
field=models.BooleanField(
|
||||||
|
default=False, verbose_name="Abkömmling gem. Satzung"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataer',
|
model_name="destinataer",
|
||||||
name='letzter_studiennachweis',
|
name="letzter_studiennachweis",
|
||||||
field=models.DateField(blank=True, null=True, verbose_name='Letzter Studiennachweis'),
|
field=models.DateField(
|
||||||
|
blank=True, null=True, verbose_name="Letzter Studiennachweis"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataer',
|
model_name="destinataer",
|
||||||
name='monatliche_bezuege',
|
name="monatliche_bezuege",
|
||||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Monatliche Bezüge (€)'),
|
field=models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Monatliche Bezüge (€)",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataer',
|
model_name="destinataer",
|
||||||
name='standard_konto',
|
name="standard_konto",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.stiftungskonto', verbose_name='Standard Auszahlungskonto'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="stiftung.stiftungskonto",
|
||||||
|
verbose_name="Standard Auszahlungskonto",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataer',
|
model_name="destinataer",
|
||||||
name='studiennachweis_erforderlich',
|
name="studiennachweis_erforderlich",
|
||||||
field=models.BooleanField(default=False, verbose_name='Studiennachweis erforderlich'),
|
field=models.BooleanField(
|
||||||
|
default=False, verbose_name="Studiennachweis erforderlich"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataer',
|
model_name="destinataer",
|
||||||
name='unterstuetzung_bestaetigt',
|
name="unterstuetzung_bestaetigt",
|
||||||
field=models.BooleanField(default=False, verbose_name='Unterstützung bestätigt'),
|
field=models.BooleanField(
|
||||||
|
default=False, verbose_name="Unterstützung bestätigt"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataer',
|
model_name="destinataer",
|
||||||
name='vermoegen',
|
name="vermoegen",
|
||||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, verbose_name='Vermögen (€)'),
|
field=models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Vermögen (€)",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,13 +6,19 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0017_destinataer_haushaltsgroesse_and_more'),
|
("stiftung", "0017_destinataer_haushaltsgroesse_and_more"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataer',
|
model_name="destinataer",
|
||||||
name='vierteljaehrlicher_betrag',
|
name="vierteljaehrlicher_betrag",
|
||||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, verbose_name='Vierteljährlicher Betrag (€)'),
|
field=models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Vierteljährlicher Betrag (€)",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,35 +1,91 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-08-29 13:40
|
# Generated by Django 5.0.6 on 2025-08-29 13:40
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0018_destinataer_vierteljaehrlicher_betrag'),
|
("stiftung", "0018_destinataer_vierteljaehrlicher_betrag"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='DestinataerUnterstuetzung',
|
name="DestinataerUnterstuetzung",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('betrag', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Betrag (€)')),
|
"id",
|
||||||
('faellig_am', models.DateField(verbose_name='Fällig am')),
|
models.UUIDField(
|
||||||
('status', models.CharField(choices=[('geplant', 'Geplant'), ('in_bearbeitung', 'In Bearbeitung'), ('ausgezahlt', 'Ausgezahlt'), ('storniert', 'Storniert')], default='geplant', max_length=20, verbose_name='Status')),
|
default=uuid.uuid4,
|
||||||
('beschreibung', models.CharField(blank=True, max_length=255, verbose_name='Beschreibung')),
|
editable=False,
|
||||||
('erstellt_am', models.DateTimeField(auto_now_add=True)),
|
primary_key=True,
|
||||||
('aktualisiert_am', models.DateTimeField(auto_now=True)),
|
serialize=False,
|
||||||
('destinataer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unterstuetzungen', to='stiftung.destinataer', verbose_name='Destinatär')),
|
),
|
||||||
('konto', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='stiftung.stiftungskonto', verbose_name='Zahlungskonto')),
|
),
|
||||||
|
(
|
||||||
|
"betrag",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2, max_digits=12, verbose_name="Betrag (€)"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("faellig_am", models.DateField(verbose_name="Fällig am")),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("geplant", "Geplant"),
|
||||||
|
("in_bearbeitung", "In Bearbeitung"),
|
||||||
|
("ausgezahlt", "Ausgezahlt"),
|
||||||
|
("storniert", "Storniert"),
|
||||||
|
],
|
||||||
|
default="geplant",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"beschreibung",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=255, verbose_name="Beschreibung"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("erstellt_am", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("aktualisiert_am", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"destinataer",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="unterstuetzungen",
|
||||||
|
to="stiftung.destinataer",
|
||||||
|
verbose_name="Destinatär",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"konto",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
to="stiftung.stiftungskonto",
|
||||||
|
verbose_name="Zahlungskonto",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Destinatärunterstützung',
|
"verbose_name": "Destinatärunterstützung",
|
||||||
'verbose_name_plural': 'Destinatärunterstützungen',
|
"verbose_name_plural": "Destinatärunterstützungen",
|
||||||
'ordering': ['-faellig_am', '-erstellt_am'],
|
"ordering": ["-faellig_am", "-erstellt_am"],
|
||||||
'indexes': [models.Index(fields=['status', 'faellig_am'], name='stiftung_de_status_1e9799_idx'), models.Index(fields=['destinataer', 'status'], name='stiftung_de_destina_ba7286_idx')],
|
"indexes": [
|
||||||
|
models.Index(
|
||||||
|
fields=["status", "faellig_am"],
|
||||||
|
name="stiftung_de_status_1e9799_idx",
|
||||||
|
),
|
||||||
|
models.Index(
|
||||||
|
fields=["destinataer", "status"],
|
||||||
|
name="stiftung_de_destina_ba7286_idx",
|
||||||
|
),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-08-29 16:05
|
# Generated by Django 5.0.6 on 2025-08-29 16:05
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
@@ -9,26 +10,65 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0019_destinataerunterstuetzung'),
|
("stiftung", "0019_destinataerunterstuetzung"),
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='DestinataerNotiz',
|
name="DestinataerNotiz",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('titel', models.CharField(blank=True, max_length=200, verbose_name='Titel')),
|
"id",
|
||||||
('text', models.TextField(blank=True, verbose_name='Notiz')),
|
models.UUIDField(
|
||||||
('datei', models.FileField(blank=True, null=True, upload_to='destinataer_notizen/', verbose_name='Anhang')),
|
default=uuid.uuid4,
|
||||||
('erstellt_am', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am')),
|
editable=False,
|
||||||
('destinataer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notizen_eintraege', to='stiftung.destinataer', verbose_name='Destinatär')),
|
primary_key=True,
|
||||||
('erstellt_von', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Erstellt von')),
|
serialize=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"titel",
|
||||||
|
models.CharField(blank=True, max_length=200, verbose_name="Titel"),
|
||||||
|
),
|
||||||
|
("text", models.TextField(blank=True, verbose_name="Notiz")),
|
||||||
|
(
|
||||||
|
"datei",
|
||||||
|
models.FileField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
upload_to="destinataer_notizen/",
|
||||||
|
verbose_name="Anhang",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"erstellt_am",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="Erstellt am"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"destinataer",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="notizen_eintraege",
|
||||||
|
to="stiftung.destinataer",
|
||||||
|
verbose_name="Destinatär",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"erstellt_von",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="Erstellt von",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Destinatär-Notiz',
|
"verbose_name": "Destinatär-Notiz",
|
||||||
'verbose_name_plural': 'Destinatär-Notizen',
|
"verbose_name_plural": "Destinatär-Notizen",
|
||||||
'ordering': ['-erstellt_am'],
|
"ordering": ["-erstellt_am"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,134 +1,354 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-08-30 14:20
|
# Generated by Django 5.0.6 on 2025-08-30 14:20
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import uuid
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0020_destinataernotiz'),
|
("stiftung", "0020_destinataernotiz"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='adresse',
|
name="adresse",
|
||||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Adresse/Ortsangabe'),
|
field=models.CharField(
|
||||||
|
blank=True, max_length=200, null=True, verbose_name="Adresse/Ortsangabe"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='aktueller_paechter',
|
name="aktueller_paechter",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='gepachtete_laendereien', to='stiftung.paechter', verbose_name='Aktueller Pächter'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="gepachtete_laendereien",
|
||||||
|
to="stiftung.paechter",
|
||||||
|
verbose_name="Aktueller Pächter",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='grundbuchblatt',
|
name="grundbuchblatt",
|
||||||
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Grundbuchblatt'),
|
field=models.CharField(
|
||||||
|
blank=True, max_length=50, null=True, verbose_name="Grundbuchblatt"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='grundsteuer_umlage',
|
name="grundsteuer_umlage",
|
||||||
field=models.BooleanField(default=True, verbose_name='Grundsteuer umlagefähig'),
|
field=models.BooleanField(
|
||||||
|
default=True, verbose_name="Grundsteuer umlagefähig"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='jagdpacht_anteil_umlage',
|
name="jagdpacht_anteil_umlage",
|
||||||
field=models.BooleanField(default=False, verbose_name='Jagdpachtanteile umlagefähig'),
|
field=models.BooleanField(
|
||||||
|
default=False, verbose_name="Jagdpachtanteile umlagefähig"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='pachtbeginn',
|
name="pachtbeginn",
|
||||||
field=models.DateField(blank=True, null=True, verbose_name='Pachtbeginn'),
|
field=models.DateField(blank=True, null=True, verbose_name="Pachtbeginn"),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='pachtende',
|
name="pachtende",
|
||||||
field=models.DateField(blank=True, null=True, verbose_name='Pachtende'),
|
field=models.DateField(blank=True, null=True, verbose_name="Pachtende"),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='pachtzins_pauschal',
|
name="pachtzins_pauschal",
|
||||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Pachtzins pauschal/Jahr (€)'),
|
field=models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
null=True,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Pachtzins pauschal/Jahr (€)",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='pachtzins_pro_ha',
|
name="pachtzins_pro_ha",
|
||||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Pachtzins pro ha (€)'),
|
field=models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
null=True,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Pachtzins pro ha (€)",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='paechter_anschrift',
|
name="paechter_anschrift",
|
||||||
field=models.TextField(blank=True, null=True, verbose_name='Pächter Anschrift'),
|
field=models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="Pächter Anschrift"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='paechter_name',
|
name="paechter_name",
|
||||||
field=models.CharField(blank=True, max_length=150, null=True, verbose_name='Pächter Name'),
|
field=models.CharField(
|
||||||
|
blank=True, max_length=150, null=True, verbose_name="Pächter Name"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='ust_option',
|
name="ust_option",
|
||||||
field=models.BooleanField(default=False, verbose_name='USt-Option'),
|
field=models.BooleanField(default=False, verbose_name="USt-Option"),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='ust_satz',
|
name="ust_satz",
|
||||||
field=models.DecimalField(decimal_places=2, default=19.0, max_digits=4, verbose_name='USt-Satz (%)'),
|
field=models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=19.0,
|
||||||
|
max_digits=4,
|
||||||
|
verbose_name="USt-Satz (%)",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='verbandsbeitraege_umlage',
|
name="verbandsbeitraege_umlage",
|
||||||
field=models.BooleanField(default=True, verbose_name='Verbandsbeiträge umlagefähig'),
|
field=models.BooleanField(
|
||||||
|
default=True, verbose_name="Verbandsbeiträge umlagefähig"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='verlaengerung_klausel',
|
name="verlaengerung_klausel",
|
||||||
field=models.BooleanField(default=False, verbose_name='Automatische Verlängerung'),
|
field=models.BooleanField(
|
||||||
|
default=False, verbose_name="Automatische Verlängerung"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='versicherungen_umlage',
|
name="versicherungen_umlage",
|
||||||
field=models.BooleanField(default=True, verbose_name='Versicherungen umlagefähig'),
|
field=models.BooleanField(
|
||||||
|
default=True, verbose_name="Versicherungen umlagefähig"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='land',
|
model_name="land",
|
||||||
name='zahlungsweise',
|
name="zahlungsweise",
|
||||||
field=models.CharField(choices=[('jaehrlich', 'Jährlich'), ('halbjaehrlich', 'Halbjährlich'), ('vierteljaehrlich', 'Vierteljährlich'), ('monatlich', 'Monatlich')], default='jaehrlich', max_length=20, verbose_name='Zahlungsweise'),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("jaehrlich", "Jährlich"),
|
||||||
|
("halbjaehrlich", "Halbjährlich"),
|
||||||
|
("vierteljaehrlich", "Vierteljährlich"),
|
||||||
|
("monatlich", "Monatlich"),
|
||||||
|
],
|
||||||
|
default="jaehrlich",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Zahlungsweise",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='LandAbrechnung',
|
name="LandAbrechnung",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('abrechnungsjahr', models.IntegerField(validators=[django.core.validators.MinValueValidator(2000)], verbose_name='Abrechnungsjahr')),
|
"id",
|
||||||
('pacht_vereinnahmt', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Pacht vereinnahmt (€)')),
|
models.UUIDField(
|
||||||
('umlagen_vereinnahmt', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Umlagen vereinnahmt (€)')),
|
default=uuid.uuid4,
|
||||||
('sonstige_einnahmen', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Sonstige Einnahmen (€)')),
|
editable=False,
|
||||||
('zahlungen', models.JSONField(blank=True, help_text='Liste von Objekten {datum, betrag, art}', null=True, verbose_name='Zahlungstermine')),
|
primary_key=True,
|
||||||
('grundsteuer_bescheid_nr', models.CharField(blank=True, max_length=80, null=True, verbose_name='Grundsteuer-Bescheid Nr.')),
|
serialize=False,
|
||||||
('grundsteuer_betrag', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Grundsteuer Betrag (€)')),
|
),
|
||||||
('versicherungen_betrag', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Versicherungen Betrag (€)')),
|
),
|
||||||
('verbandsbeitraege_betrag', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Verbandsbeiträge Betrag (€)')),
|
(
|
||||||
('sonstige_abgaben_betrag', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Sonstige öffentliche Abgaben (€)')),
|
"abrechnungsjahr",
|
||||||
('instandhaltung_betrag', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Instandhaltung/Reparaturen (€)')),
|
models.IntegerField(
|
||||||
('verwaltung_recht_betrag', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Verwaltung/Recht (€)')),
|
validators=[django.core.validators.MinValueValidator(2000)],
|
||||||
('vorsteuer_aus_umlagen', models.DecimalField(decimal_places=2, default=0, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Vorsteuer aus umgelegten Kosten (€)')),
|
verbose_name="Abrechnungsjahr",
|
||||||
('offene_posten', models.DecimalField(decimal_places=2, default=0, max_digits=12, verbose_name='Offene Posten (€)')),
|
),
|
||||||
('bemerkungen', models.TextField(blank=True, null=True, verbose_name='Bemerkungen Abrechnung')),
|
),
|
||||||
('pachtvertrag_datei', models.FileField(blank=True, null=True, upload_to='land_abrechnungen/vertraege/', verbose_name='Pachtvertrag (Datei)')),
|
(
|
||||||
('grundsteuer_bescheid_datei', models.FileField(blank=True, null=True, upload_to='land_abrechnungen/bescheide/', verbose_name='Grundsteuerbescheid (Datei)')),
|
"pacht_vereinnahmt",
|
||||||
('versicherungsnachweis_datei', models.FileField(blank=True, null=True, upload_to='land_abrechnungen/versicherungen/', verbose_name='Versicherungsnachweis (Datei)')),
|
models.DecimalField(
|
||||||
('erstellt_am', models.DateTimeField(auto_now_add=True)),
|
decimal_places=2,
|
||||||
('aktualisiert_am', models.DateTimeField(auto_now=True)),
|
default=0,
|
||||||
('land', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='abrechnungen', to='stiftung.land', verbose_name='Länderei')),
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Pacht vereinnahmt (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"umlagen_vereinnahmt",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Umlagen vereinnahmt (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"sonstige_einnahmen",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Sonstige Einnahmen (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zahlungen",
|
||||||
|
models.JSONField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Liste von Objekten {datum, betrag, art}",
|
||||||
|
null=True,
|
||||||
|
verbose_name="Zahlungstermine",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"grundsteuer_bescheid_nr",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=80,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Grundsteuer-Bescheid Nr.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"grundsteuer_betrag",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Grundsteuer Betrag (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"versicherungen_betrag",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Versicherungen Betrag (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"verbandsbeitraege_betrag",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Verbandsbeiträge Betrag (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"sonstige_abgaben_betrag",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Sonstige öffentliche Abgaben (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"instandhaltung_betrag",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Instandhaltung/Reparaturen (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"verwaltung_recht_betrag",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Verwaltung/Recht (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vorsteuer_aus_umlagen",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Vorsteuer aus umgelegten Kosten (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"offene_posten",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=0,
|
||||||
|
max_digits=12,
|
||||||
|
verbose_name="Offene Posten (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"bemerkungen",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="Bemerkungen Abrechnung"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pachtvertrag_datei",
|
||||||
|
models.FileField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
upload_to="land_abrechnungen/vertraege/",
|
||||||
|
verbose_name="Pachtvertrag (Datei)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"grundsteuer_bescheid_datei",
|
||||||
|
models.FileField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
upload_to="land_abrechnungen/bescheide/",
|
||||||
|
verbose_name="Grundsteuerbescheid (Datei)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"versicherungsnachweis_datei",
|
||||||
|
models.FileField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
upload_to="land_abrechnungen/versicherungen/",
|
||||||
|
verbose_name="Versicherungsnachweis (Datei)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("erstellt_am", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("aktualisiert_am", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"land",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="abrechnungen",
|
||||||
|
to="stiftung.land",
|
||||||
|
verbose_name="Länderei",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Landabrechnung',
|
"verbose_name": "Landabrechnung",
|
||||||
'verbose_name_plural': 'Landabrechnungen',
|
"verbose_name_plural": "Landabrechnungen",
|
||||||
'ordering': ['-abrechnungsjahr', 'land__gemeinde', 'land__gemarkung'],
|
"ordering": ["-abrechnungsjahr", "land__gemeinde", "land__gemarkung"],
|
||||||
'unique_together': {('land', 'abrechnungsjahr')},
|
"unique_together": {("land", "abrechnungsjahr")},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,57 +1,185 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-08-30 16:59
|
# Generated by Django 5.0.6 on 2025-08-30 16:59
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import uuid
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0021_land_adresse_land_aktueller_paechter_and_more'),
|
("stiftung", "0021_land_adresse_land_aktueller_paechter_and_more"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='land_verpachtung_id',
|
name="land_verpachtung_id",
|
||||||
field=models.UUIDField(blank=True, null=True, verbose_name='Landverpachtung ID (Neu)'),
|
field=models.UUIDField(
|
||||||
|
blank=True, null=True, verbose_name="Landverpachtung ID (Neu)"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='verpachtung_id',
|
name="verpachtung_id",
|
||||||
field=models.UUIDField(blank=True, null=True, verbose_name='Verpachtung ID (Legacy)'),
|
field=models.UUIDField(
|
||||||
|
blank=True, null=True, verbose_name="Verpachtung ID (Legacy)"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='LandVerpachtung',
|
name="LandVerpachtung",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('vertragsnummer', models.CharField(max_length=50, unique=True, verbose_name='Vertragsnummer')),
|
"id",
|
||||||
('pachtbeginn', models.DateField(verbose_name='Pachtbeginn')),
|
models.UUIDField(
|
||||||
('pachtende', models.DateField(blank=True, null=True, verbose_name='Pachtende')),
|
default=uuid.uuid4,
|
||||||
('verlaengerung_klausel', models.BooleanField(default=False, verbose_name='Automatische Verlängerung')),
|
editable=False,
|
||||||
('verpachtete_flaeche', models.DecimalField(decimal_places=2, max_digits=12, validators=[django.core.validators.MinValueValidator(0.01)], verbose_name='Verpachtete Fläche (qm)')),
|
primary_key=True,
|
||||||
('pachtzins_pauschal', models.DecimalField(decimal_places=2, max_digits=12, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Pachtzins pauschal/Jahr (€)')),
|
serialize=False,
|
||||||
('pachtzins_pro_ha', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Pachtzins pro ha (€)')),
|
),
|
||||||
('zahlungsweise', models.CharField(choices=[('jaehrlich', 'Jährlich'), ('halbjaehrlich', 'Halbjährlich'), ('vierteljaehrlich', 'Vierteljährlich'), ('monatlich', 'Monatlich')], default='jaehrlich', max_length=20, verbose_name='Zahlungsweise')),
|
),
|
||||||
('ust_option', models.BooleanField(default=False, verbose_name='USt-Option')),
|
(
|
||||||
('ust_satz', models.DecimalField(decimal_places=2, default=19.0, max_digits=4, verbose_name='USt-Satz (%)')),
|
"vertragsnummer",
|
||||||
('grundsteuer_umlage', models.BooleanField(default=True, verbose_name='Grundsteuer umlagefähig')),
|
models.CharField(
|
||||||
('versicherungen_umlage', models.BooleanField(default=True, verbose_name='Versicherungen umlagefähig')),
|
max_length=50, unique=True, verbose_name="Vertragsnummer"
|
||||||
('verbandsbeitraege_umlage', models.BooleanField(default=True, verbose_name='Verbandsbeiträge umlagefähig')),
|
),
|
||||||
('jagdpacht_anteil_umlage', models.BooleanField(default=False, verbose_name='Jagdpachtanteile umlagefähig')),
|
),
|
||||||
('status', models.CharField(choices=[('aktiv', 'Aktiv'), ('beendet', 'Beendet'), ('gekuendigt', 'Gekündigt'), ('verlängert', 'Verlängert')], default='aktiv', max_length=20, verbose_name='Status')),
|
("pachtbeginn", models.DateField(verbose_name="Pachtbeginn")),
|
||||||
('bemerkungen', models.TextField(blank=True, null=True, verbose_name='Bemerkungen')),
|
(
|
||||||
('erstellt_am', models.DateTimeField(auto_now_add=True)),
|
"pachtende",
|
||||||
('aktualisiert_am', models.DateTimeField(auto_now=True)),
|
models.DateField(blank=True, null=True, verbose_name="Pachtende"),
|
||||||
('land', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='neue_verpachtungen', to='stiftung.land', verbose_name='Länderei')),
|
),
|
||||||
('paechter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='neue_verpachtungen', to='stiftung.paechter', verbose_name='Pächter')),
|
(
|
||||||
|
"verlaengerung_klausel",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False, verbose_name="Automatische Verlängerung"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"verpachtete_flaeche",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0.01)],
|
||||||
|
verbose_name="Verpachtete Fläche (qm)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pachtzins_pauschal",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Pachtzins pauschal/Jahr (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pachtzins_pro_ha",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=12,
|
||||||
|
null=True,
|
||||||
|
validators=[django.core.validators.MinValueValidator(0)],
|
||||||
|
verbose_name="Pachtzins pro ha (€)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zahlungsweise",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("jaehrlich", "Jährlich"),
|
||||||
|
("halbjaehrlich", "Halbjährlich"),
|
||||||
|
("vierteljaehrlich", "Vierteljährlich"),
|
||||||
|
("monatlich", "Monatlich"),
|
||||||
|
],
|
||||||
|
default="jaehrlich",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Zahlungsweise",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ust_option",
|
||||||
|
models.BooleanField(default=False, verbose_name="USt-Option"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ust_satz",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=2,
|
||||||
|
default=19.0,
|
||||||
|
max_digits=4,
|
||||||
|
verbose_name="USt-Satz (%)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"grundsteuer_umlage",
|
||||||
|
models.BooleanField(
|
||||||
|
default=True, verbose_name="Grundsteuer umlagefähig"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"versicherungen_umlage",
|
||||||
|
models.BooleanField(
|
||||||
|
default=True, verbose_name="Versicherungen umlagefähig"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"verbandsbeitraege_umlage",
|
||||||
|
models.BooleanField(
|
||||||
|
default=True, verbose_name="Verbandsbeiträge umlagefähig"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"jagdpacht_anteil_umlage",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False, verbose_name="Jagdpachtanteile umlagefähig"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("aktiv", "Aktiv"),
|
||||||
|
("beendet", "Beendet"),
|
||||||
|
("gekuendigt", "Gekündigt"),
|
||||||
|
("verlängert", "Verlängert"),
|
||||||
|
],
|
||||||
|
default="aktiv",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"bemerkungen",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="Bemerkungen"),
|
||||||
|
),
|
||||||
|
("erstellt_am", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("aktualisiert_am", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"land",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="neue_verpachtungen",
|
||||||
|
to="stiftung.land",
|
||||||
|
verbose_name="Länderei",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"paechter",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="neue_verpachtungen",
|
||||||
|
to="stiftung.paechter",
|
||||||
|
verbose_name="Pächter",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Landverpachtung',
|
"verbose_name": "Landverpachtung",
|
||||||
'verbose_name_plural': 'Landverpachtungen',
|
"verbose_name_plural": "Landverpachtungen",
|
||||||
'ordering': ['-pachtbeginn', 'land'],
|
"ordering": ["-pachtbeginn", "land"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ from django.db import migrations
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0022_dokumentlink_land_verpachtung_id_and_more'),
|
("stiftung", "0022_dokumentlink_land_verpachtung_id_and_more"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.DeleteModel(
|
migrations.DeleteModel(
|
||||||
name='Verpachtung',
|
name="Verpachtung",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0023_remove_legacy_verpachtung'),
|
("stiftung", "0023_remove_legacy_verpachtung"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='dokumentlink',
|
model_name="dokumentlink",
|
||||||
name='abrechnung_id',
|
name="abrechnung_id",
|
||||||
field=models.UUIDField(blank=True, null=True, verbose_name='Abrechnung ID'),
|
field=models.UUIDField(blank=True, null=True, verbose_name="Abrechnung ID"),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,37 +1,90 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-08-31 22:08
|
# Generated by Django 5.0.6 on 2025-08-31 22:08
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0024_dokumentlink_abrechnung_id'),
|
("stiftung", "0024_dokumentlink_abrechnung_id"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='AppConfiguration',
|
name="AppConfiguration",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('key', models.CharField(max_length=100, unique=True, verbose_name='Setting Key')),
|
"id",
|
||||||
('display_name', models.CharField(max_length=200, verbose_name='Display Name')),
|
models.UUIDField(
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='Description')),
|
default=uuid.uuid4,
|
||||||
('value', models.TextField(verbose_name='Value')),
|
editable=False,
|
||||||
('default_value', models.TextField(verbose_name='Default Value')),
|
primary_key=True,
|
||||||
('setting_type', models.CharField(choices=[('text', 'Text'), ('number', 'Number'), ('boolean', 'Boolean'), ('url', 'URL'), ('tag', 'Tag Name'), ('tag_id', 'Tag ID')], default='text', max_length=20, verbose_name='Type')),
|
serialize=False,
|
||||||
('category', models.CharField(choices=[('paperless', 'Paperless Integration'), ('general', 'General Settings'), ('notifications', 'Notifications'), ('system', 'System Settings')], default='general', max_length=50, verbose_name='Category')),
|
),
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='Active')),
|
),
|
||||||
('is_system', models.BooleanField(default=False, verbose_name='System Setting (read-only)')),
|
(
|
||||||
('order', models.IntegerField(default=0, verbose_name='Display Order')),
|
"key",
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
models.CharField(
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
max_length=100, unique=True, verbose_name="Setting Key"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"display_name",
|
||||||
|
models.CharField(max_length=200, verbose_name="Display Name"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="Description"),
|
||||||
|
),
|
||||||
|
("value", models.TextField(verbose_name="Value")),
|
||||||
|
("default_value", models.TextField(verbose_name="Default Value")),
|
||||||
|
(
|
||||||
|
"setting_type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("text", "Text"),
|
||||||
|
("number", "Number"),
|
||||||
|
("boolean", "Boolean"),
|
||||||
|
("url", "URL"),
|
||||||
|
("tag", "Tag Name"),
|
||||||
|
("tag_id", "Tag ID"),
|
||||||
|
],
|
||||||
|
default="text",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Type",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"category",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("paperless", "Paperless Integration"),
|
||||||
|
("general", "General Settings"),
|
||||||
|
("notifications", "Notifications"),
|
||||||
|
("system", "System Settings"),
|
||||||
|
],
|
||||||
|
default="general",
|
||||||
|
max_length=50,
|
||||||
|
verbose_name="Category",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("is_active", models.BooleanField(default=True, verbose_name="Active")),
|
||||||
|
(
|
||||||
|
"is_system",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False, verbose_name="System Setting (read-only)"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("order", models.IntegerField(default=0, verbose_name="Display Order")),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'App Configuration',
|
"verbose_name": "App Configuration",
|
||||||
'verbose_name_plural': 'App Configurations',
|
"verbose_name_plural": "App Configurations",
|
||||||
'ordering': ['category', 'order', 'display_name'],
|
"ordering": ["category", "order", "display_name"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-09-01 20:03
|
# Generated by Django 5.0.6 on 2025-09-01 20:03
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
@@ -9,81 +10,192 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0025_appconfiguration'),
|
("stiftung", "0025_appconfiguration"),
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataerunterstuetzung',
|
model_name="destinataerunterstuetzung",
|
||||||
name='ausgezahlt_am',
|
name="ausgezahlt_am",
|
||||||
field=models.DateField(blank=True, null=True, verbose_name='Ausgezahlt am'),
|
field=models.DateField(blank=True, null=True, verbose_name="Ausgezahlt am"),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataerunterstuetzung',
|
model_name="destinataerunterstuetzung",
|
||||||
name='ausgezahlt_von',
|
name="ausgezahlt_von",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Ausgezahlt von'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="Ausgezahlt von",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataerunterstuetzung',
|
model_name="destinataerunterstuetzung",
|
||||||
name='empfaenger_iban',
|
name="empfaenger_iban",
|
||||||
field=models.CharField(blank=True, max_length=34, verbose_name='Empfänger IBAN'),
|
field=models.CharField(
|
||||||
|
blank=True, max_length=34, verbose_name="Empfänger IBAN"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataerunterstuetzung',
|
model_name="destinataerunterstuetzung",
|
||||||
name='empfaenger_name',
|
name="empfaenger_name",
|
||||||
field=models.CharField(blank=True, max_length=200, verbose_name='Empfänger Name'),
|
field=models.CharField(
|
||||||
|
blank=True, max_length=200, verbose_name="Empfänger Name"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataerunterstuetzung',
|
model_name="destinataerunterstuetzung",
|
||||||
name='verwendungszweck',
|
name="verwendungszweck",
|
||||||
field=models.CharField(blank=True, max_length=140, verbose_name='Verwendungszweck'),
|
field=models.CharField(
|
||||||
|
blank=True, max_length=140, verbose_name="Verwendungszweck"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='destinataerunterstuetzung',
|
model_name="destinataerunterstuetzung",
|
||||||
name='status',
|
name="status",
|
||||||
field=models.CharField(choices=[('geplant', 'Geplant'), ('faellig', 'Fällig'), ('in_bearbeitung', 'In Bearbeitung'), ('ausgezahlt', 'Ausgezahlt'), ('storniert', 'Storniert')], default='geplant', max_length=20, verbose_name='Status'),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("geplant", "Geplant"),
|
||||||
|
("faellig", "Fällig"),
|
||||||
|
("in_bearbeitung", "In Bearbeitung"),
|
||||||
|
("ausgezahlt", "Ausgezahlt"),
|
||||||
|
("storniert", "Storniert"),
|
||||||
|
],
|
||||||
|
default="geplant",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Status",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='UnterstuetzungWiederkehrend',
|
name="UnterstuetzungWiederkehrend",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('betrag', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Betrag (€)')),
|
"id",
|
||||||
('intervall', models.CharField(choices=[('monatlich', 'Monatlich'), ('quartalsweise', 'Vierteljährlich'), ('halbjaehrlich', 'Halbjährlich'), ('jaehrlich', 'Jährlich')], max_length=20, verbose_name='Intervall')),
|
models.UUIDField(
|
||||||
('beschreibung', models.CharField(blank=True, max_length=255, verbose_name='Beschreibung')),
|
default=uuid.uuid4,
|
||||||
('empfaenger_iban', models.CharField(max_length=34, verbose_name='Empfänger IBAN')),
|
editable=False,
|
||||||
('empfaenger_name', models.CharField(max_length=200, verbose_name='Empfänger Name')),
|
primary_key=True,
|
||||||
('verwendungszweck', models.CharField(blank=True, max_length=140, verbose_name='Verwendungszweck')),
|
serialize=False,
|
||||||
('erste_zahlung_am', models.DateField(verbose_name='Erste Zahlung am')),
|
),
|
||||||
('letzte_zahlung_am', models.DateField(blank=True, null=True, verbose_name='Letzte Zahlung am (optional)')),
|
),
|
||||||
('naechste_generierung', models.DateField(verbose_name='Nächste Generierung')),
|
(
|
||||||
('aktiv', models.BooleanField(default=True, verbose_name='Aktiv')),
|
"betrag",
|
||||||
('erstellt_am', models.DateTimeField(auto_now_add=True)),
|
models.DecimalField(
|
||||||
('destinataer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wiederkehrende_unterstuetzungen', to='stiftung.destinataer', verbose_name='Destinatär')),
|
decimal_places=2, max_digits=12, verbose_name="Betrag (€)"
|
||||||
('erstellt_von', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Erstellt von')),
|
),
|
||||||
('konto', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='stiftung.stiftungskonto', verbose_name='Zahlungskonto')),
|
),
|
||||||
|
(
|
||||||
|
"intervall",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("monatlich", "Monatlich"),
|
||||||
|
("quartalsweise", "Vierteljährlich"),
|
||||||
|
("halbjaehrlich", "Halbjährlich"),
|
||||||
|
("jaehrlich", "Jährlich"),
|
||||||
|
],
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="Intervall",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"beschreibung",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=255, verbose_name="Beschreibung"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"empfaenger_iban",
|
||||||
|
models.CharField(max_length=34, verbose_name="Empfänger IBAN"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"empfaenger_name",
|
||||||
|
models.CharField(max_length=200, verbose_name="Empfänger Name"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"verwendungszweck",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=140, verbose_name="Verwendungszweck"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("erste_zahlung_am", models.DateField(verbose_name="Erste Zahlung am")),
|
||||||
|
(
|
||||||
|
"letzte_zahlung_am",
|
||||||
|
models.DateField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Letzte Zahlung am (optional)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"naechste_generierung",
|
||||||
|
models.DateField(verbose_name="Nächste Generierung"),
|
||||||
|
),
|
||||||
|
("aktiv", models.BooleanField(default=True, verbose_name="Aktiv")),
|
||||||
|
("erstellt_am", models.DateTimeField(auto_now_add=True)),
|
||||||
|
(
|
||||||
|
"destinataer",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wiederkehrende_unterstuetzungen",
|
||||||
|
to="stiftung.destinataer",
|
||||||
|
verbose_name="Destinatär",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"erstellt_von",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="Erstellt von",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"konto",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
to="stiftung.stiftungskonto",
|
||||||
|
verbose_name="Zahlungskonto",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Wiederkehrende Unterstützung',
|
"verbose_name": "Wiederkehrende Unterstützung",
|
||||||
'verbose_name_plural': 'Wiederkehrende Unterstützungen',
|
"verbose_name_plural": "Wiederkehrende Unterstützungen",
|
||||||
'ordering': ['-erstellt_am'],
|
"ordering": ["-erstellt_am"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='destinataerunterstuetzung',
|
model_name="destinataerunterstuetzung",
|
||||||
name='wiederkehrend_von',
|
name="wiederkehrend_von",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.unterstuetzungwiederkehrend', verbose_name='Wiederkehrende Zahlung'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="stiftung.unterstuetzungwiederkehrend",
|
||||||
|
verbose_name="Wiederkehrende Zahlung",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddIndex(
|
migrations.AddIndex(
|
||||||
model_name='destinataerunterstuetzung',
|
model_name="destinataerunterstuetzung",
|
||||||
index=models.Index(fields=['wiederkehrend_von'], name='stiftung_de_wiederk_3d5afc_idx'),
|
index=models.Index(
|
||||||
|
fields=["wiederkehrend_von"], name="stiftung_de_wiederk_3d5afc_idx"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddIndex(
|
migrations.AddIndex(
|
||||||
model_name='unterstuetzungwiederkehrend',
|
model_name="unterstuetzungwiederkehrend",
|
||||||
index=models.Index(fields=['aktiv', 'naechste_generierung'], name='stiftung_un_aktiv_b957e5_idx'),
|
index=models.Index(
|
||||||
|
fields=["aktiv", "naechste_generierung"],
|
||||||
|
name="stiftung_un_aktiv_b957e5_idx",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddIndex(
|
migrations.AddIndex(
|
||||||
model_name='unterstuetzungwiederkehrend',
|
model_name="unterstuetzungwiederkehrend",
|
||||||
index=models.Index(fields=['destinataer', 'aktiv'], name='stiftung_un_destina_2232fc_idx'),
|
index=models.Index(
|
||||||
|
fields=["destinataer", "aktiv"], name="stiftung_un_destina_2232fc_idx"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,38 +1,106 @@
|
|||||||
# Generated by Django 5.0.6 on 2025-09-02 19:56
|
# Generated by Django 5.0.6 on 2025-09-02 19:56
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0026_enhance_unterstuetzung_model'),
|
("stiftung", "0026_enhance_unterstuetzung_model"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='HelpBox',
|
name="HelpBox",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('page_key', models.CharField(choices=[('destinataer_new', 'Neuer Destinatär'), ('unterstuetzung_new', 'Neue Unterstützung'), ('foerderung_new', 'Neue Förderung'), ('paechter_new', 'Neuer Pächter'), ('laenderei_new', 'Neue Länderei'), ('verpachtung_new', 'Neue Verpachtung'), ('person_new', 'Neue Person'), ('konto_new', 'Neues Konto')], max_length=50, unique=True, verbose_name='Seite')),
|
"id",
|
||||||
('title', models.CharField(max_length=200, verbose_name='Titel der Hilfsbox')),
|
models.UUIDField(
|
||||||
('content', models.TextField(help_text='Sie können Markdown verwenden: **fett**, *kursiv*, `code`, [Link](url), etc.', verbose_name='Inhalt (Markdown unterstützt)')),
|
default=uuid.uuid4,
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='Aktiv')),
|
editable=False,
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am')),
|
primary_key=True,
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Aktualisiert am')),
|
serialize=False,
|
||||||
('created_by', models.CharField(blank=True, max_length=100, null=True, verbose_name='Erstellt von')),
|
),
|
||||||
('updated_by', models.CharField(blank=True, max_length=100, null=True, verbose_name='Aktualisiert von')),
|
),
|
||||||
|
(
|
||||||
|
"page_key",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("destinataer_new", "Neuer Destinatär"),
|
||||||
|
("unterstuetzung_new", "Neue Unterstützung"),
|
||||||
|
("foerderung_new", "Neue Förderung"),
|
||||||
|
("paechter_new", "Neuer Pächter"),
|
||||||
|
("laenderei_new", "Neue Länderei"),
|
||||||
|
("verpachtung_new", "Neue Verpachtung"),
|
||||||
|
("person_new", "Neue Person"),
|
||||||
|
("konto_new", "Neues Konto"),
|
||||||
|
],
|
||||||
|
max_length=50,
|
||||||
|
unique=True,
|
||||||
|
verbose_name="Seite",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title",
|
||||||
|
models.CharField(max_length=200, verbose_name="Titel der Hilfsbox"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"content",
|
||||||
|
models.TextField(
|
||||||
|
help_text="Sie können Markdown verwenden: **fett**, *kursiv*, `code`, [Link](url), etc.",
|
||||||
|
verbose_name="Inhalt (Markdown unterstützt)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("is_active", models.BooleanField(default=True, verbose_name="Aktiv")),
|
||||||
|
(
|
||||||
|
"created_at",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="Erstellt am"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"updated_at",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="Aktualisiert am"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created_by",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=100,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Erstellt von",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"updated_by",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=100,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Aktualisiert von",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Hilfs-Infobox',
|
"verbose_name": "Hilfs-Infobox",
|
||||||
'verbose_name_plural': 'Hilfs-Infoboxen',
|
"verbose_name_plural": "Hilfs-Infoboxen",
|
||||||
'ordering': ['page_key'],
|
"ordering": ["page_key"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='appconfiguration',
|
model_name="appconfiguration",
|
||||||
name='category',
|
name="category",
|
||||||
field=models.CharField(choices=[('paperless', 'Paperless Integration'), ('general', 'General Settings'), ('corporate', 'Corporate Identity'), ('notifications', 'Notifications'), ('system', 'System Settings')], default='general', max_length=50, verbose_name='Category'),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("paperless", "Paperless Integration"),
|
||||||
|
("general", "General Settings"),
|
||||||
|
("corporate", "Corporate Identity"),
|
||||||
|
("notifications", "Notifications"),
|
||||||
|
("system", "System Settings"),
|
||||||
|
],
|
||||||
|
default="general",
|
||||||
|
max_length=50,
|
||||||
|
verbose_name="Category",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,13 +6,34 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stiftung', '0027_helpbox_alter_appconfiguration_category'),
|
("stiftung", "0027_helpbox_alter_appconfiguration_category"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='helpbox',
|
model_name="helpbox",
|
||||||
name='page_key',
|
name="page_key",
|
||||||
field=models.CharField(choices=[('destinataer_new', 'Neuer Destinatär'), ('unterstuetzung_new', 'Neue Unterstützung'), ('foerderung_new', 'Neue Förderung'), ('paechter_new', 'Neuer Pächter'), ('laenderei_new', 'Neue Länderei'), ('verpachtung_new', 'Neue Verpachtung'), ('land_abrechnung_new', 'Neue Landabrechnung'), ('person_new', 'Neue Person'), ('konto_new', 'Neues Konto'), ('verwaltungskosten_new', 'Neue Verwaltungskosten'), ('rentmeister_new', 'Neuer Rentmeister'), ('dokument_new', 'Neues Dokument'), ('user_new', 'Neuer Benutzer'), ('csv_import_new', 'CSV Import'), ('destinataer_notiz_new', 'Destinatär Notiz')], max_length=50, unique=True, verbose_name='Seite'),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("destinataer_new", "Neuer Destinatär"),
|
||||||
|
("unterstuetzung_new", "Neue Unterstützung"),
|
||||||
|
("foerderung_new", "Neue Förderung"),
|
||||||
|
("paechter_new", "Neuer Pächter"),
|
||||||
|
("laenderei_new", "Neue Länderei"),
|
||||||
|
("verpachtung_new", "Neue Verpachtung"),
|
||||||
|
("land_abrechnung_new", "Neue Landabrechnung"),
|
||||||
|
("person_new", "Neue Person"),
|
||||||
|
("konto_new", "Neues Konto"),
|
||||||
|
("verwaltungskosten_new", "Neue Verwaltungskosten"),
|
||||||
|
("rentmeister_new", "Neuer Rentmeister"),
|
||||||
|
("dokument_new", "Neues Dokument"),
|
||||||
|
("user_new", "Neuer Benutzer"),
|
||||||
|
("csv_import_new", "CSV Import"),
|
||||||
|
("destinataer_notiz_new", "Destinatär Notiz"),
|
||||||
|
],
|
||||||
|
max_length=50,
|
||||||
|
unique=True,
|
||||||
|
verbose_name="Seite",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,14 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import Person, Foerderung
|
|
||||||
|
from .models import Foerderung, Person
|
||||||
|
|
||||||
|
|
||||||
class PersonSerializer(serializers.ModelSerializer):
|
class PersonSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Person
|
model = Person
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class FoerderungSerializer(serializers.ModelSerializer):
|
class FoerderungSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Foerderung
|
model = Foerderung
|
||||||
|
|||||||
@@ -1,28 +1,33 @@
|
|||||||
import markdown
|
import markdown
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from stiftung.models import HelpBox
|
from stiftung.models import HelpBox
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@register.inclusion_tag('stiftung/help_box.html')
|
|
||||||
|
@register.inclusion_tag("stiftung/help_box.html")
|
||||||
def help_box(page_key, user=None):
|
def help_box(page_key, user=None):
|
||||||
"""Rendere eine Hilfs-Infobox für eine bestimmte Seite"""
|
"""Rendere eine Hilfs-Infobox für eine bestimmte Seite"""
|
||||||
help_obj = HelpBox.get_help_for_page(page_key)
|
help_obj = HelpBox.get_help_for_page(page_key)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'help_obj': help_obj,
|
"help_obj": help_obj,
|
||||||
'page_key': page_key,
|
"page_key": page_key,
|
||||||
'can_edit': user and (user.username == 'root' or user.is_superuser) if user else False
|
"can_edit": (
|
||||||
|
user and (user.username == "root" or user.is_superuser) if user else False
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
if help_obj:
|
if help_obj:
|
||||||
# Konvertiere Markdown zu HTML
|
# Konvertiere Markdown zu HTML
|
||||||
md = markdown.Markdown(extensions=['nl2br', 'fenced_code'])
|
md = markdown.Markdown(extensions=["nl2br", "fenced_code"])
|
||||||
context['content_html'] = mark_safe(md.convert(help_obj.content))
|
context["content_html"] = mark_safe(md.convert(help_obj.content))
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def help_box_exists(page_key):
|
def help_box_exists(page_key):
|
||||||
"""Prüfe, ob eine Hilfs-Infobox für eine Seite existiert"""
|
"""Prüfe, ob eine Hilfs-Infobox für eine Seite existiert"""
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
PDF-specific template tags and filters
|
PDF-specific template tags and filters
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
@@ -15,14 +16,14 @@ def lookup(obj, field_name):
|
|||||||
"""
|
"""
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Handle dict-like objects
|
# Handle dict-like objects
|
||||||
if hasattr(obj, '__getitem__') and not isinstance(obj, str):
|
if hasattr(obj, "__getitem__") and not isinstance(obj, str):
|
||||||
try:
|
try:
|
||||||
return obj[field_name]
|
return obj[field_name]
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Handle objects with attributes
|
# Handle objects with attributes
|
||||||
if hasattr(obj, field_name):
|
if hasattr(obj, field_name):
|
||||||
attr = getattr(obj, field_name)
|
attr = getattr(obj, field_name)
|
||||||
@@ -34,17 +35,17 @@ def lookup(obj, field_name):
|
|||||||
# Method requires arguments, return as is
|
# Method requires arguments, return as is
|
||||||
return attr
|
return attr
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
# Try to handle nested field access (e.g., "person.name")
|
# Try to handle nested field access (e.g., "person.name")
|
||||||
if '.' in field_name:
|
if "." in field_name:
|
||||||
parts = field_name.split('.')
|
parts = field_name.split(".")
|
||||||
current = obj
|
current = obj
|
||||||
for part in parts:
|
for part in parts:
|
||||||
if current is None:
|
if current is None:
|
||||||
return None
|
return None
|
||||||
current = lookup(current, part)
|
current = lookup(current, part)
|
||||||
return current
|
return current
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -55,14 +56,14 @@ def get_display_value(obj, field_name):
|
|||||||
Usage: {{ object|get_display_value:"field_name" }}
|
Usage: {{ object|get_display_value:"field_name" }}
|
||||||
"""
|
"""
|
||||||
value = lookup(obj, field_name)
|
value = lookup(obj, field_name)
|
||||||
|
|
||||||
# Try to get display value for choice fields
|
# Try to get display value for choice fields
|
||||||
display_method = f'get_{field_name}_display'
|
display_method = f"get_{field_name}_display"
|
||||||
if hasattr(obj, display_method):
|
if hasattr(obj, display_method):
|
||||||
display_value = getattr(obj, display_method)()
|
display_value = getattr(obj, display_method)()
|
||||||
if display_value:
|
if display_value:
|
||||||
return display_value
|
return display_value
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@@ -73,9 +74,9 @@ def format_currency(value):
|
|||||||
Usage: {{ value|format_currency }}
|
Usage: {{ value|format_currency }}
|
||||||
"""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return '-'
|
return "-"
|
||||||
try:
|
try:
|
||||||
return f"€{float(value):,.2f}".replace(',', ' ')
|
return f"€{float(value):,.2f}".replace(",", " ")
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
@@ -87,11 +88,11 @@ def format_status_badge(status):
|
|||||||
Usage: {{ status|format_status_badge }}
|
Usage: {{ status|format_status_badge }}
|
||||||
"""
|
"""
|
||||||
if not status:
|
if not status:
|
||||||
return '-'
|
return "-"
|
||||||
|
|
||||||
status_lower = str(status).lower()
|
status_lower = str(status).lower()
|
||||||
css_class = f'status-{status_lower}'
|
css_class = f"status-{status_lower}"
|
||||||
|
|
||||||
return mark_safe(f'<span class="status-badge {css_class}">{status}</span>')
|
return mark_safe(f'<span class="status-badge {css_class}">{status}</span>')
|
||||||
|
|
||||||
|
|
||||||
@@ -102,13 +103,13 @@ def truncate_field(value, max_length=50):
|
|||||||
Usage: {{ value|truncate_field:30 }}
|
Usage: {{ value|truncate_field:30 }}
|
||||||
"""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return '-'
|
return "-"
|
||||||
|
|
||||||
str_value = str(value)
|
str_value = str(value)
|
||||||
if len(str_value) <= max_length:
|
if len(str_value) <= max_length:
|
||||||
return str_value
|
return str_value
|
||||||
|
|
||||||
return str_value[:max_length-3] + '...'
|
return str_value[: max_length - 3] + "..."
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
@@ -117,29 +118,29 @@ def get_field_value(obj, field_config):
|
|||||||
Get formatted field value based on field configuration
|
Get formatted field value based on field configuration
|
||||||
Usage: {% get_field_value object field_config %}
|
Usage: {% get_field_value object field_config %}
|
||||||
"""
|
"""
|
||||||
field_name = field_config.get('field_name')
|
field_name = field_config.get("field_name")
|
||||||
field_type = field_config.get('field_type', 'text')
|
field_type = field_config.get("field_type", "text")
|
||||||
|
|
||||||
value = lookup(obj, field_name)
|
value = lookup(obj, field_name)
|
||||||
|
|
||||||
if value is None:
|
if value is None:
|
||||||
return '-'
|
return "-"
|
||||||
|
|
||||||
if field_type == 'currency':
|
if field_type == "currency":
|
||||||
return format_currency(value)
|
return format_currency(value)
|
||||||
elif field_type == 'date':
|
elif field_type == "date":
|
||||||
try:
|
try:
|
||||||
return value.strftime('%d.%m.%Y')
|
return value.strftime("%d.%m.%Y")
|
||||||
except (AttributeError, ValueError):
|
except (AttributeError, ValueError):
|
||||||
return str(value)
|
return str(value)
|
||||||
elif field_type == 'datetime':
|
elif field_type == "datetime":
|
||||||
try:
|
try:
|
||||||
return value.strftime('%d.%m.%Y %H:%M')
|
return value.strftime("%d.%m.%Y %H:%M")
|
||||||
except (AttributeError, ValueError):
|
except (AttributeError, ValueError):
|
||||||
return str(value)
|
return str(value)
|
||||||
elif field_type == 'status':
|
elif field_type == "status":
|
||||||
return format_status_badge(value)
|
return format_status_badge(value)
|
||||||
elif field_type == 'boolean':
|
elif field_type == "boolean":
|
||||||
return 'Ja' if value else 'Nein'
|
return "Ja" if value else "Nein"
|
||||||
else:
|
else:
|
||||||
return truncate_field(value)
|
return truncate_field(value)
|
||||||
|
|||||||
@@ -1,160 +1,337 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
app_name = 'stiftung'
|
app_name = "stiftung"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Dashboard (Startseite)
|
# Dashboard (Startseite)
|
||||||
path('', views.dashboard, name='dashboard'),
|
path("", views.dashboard, name="dashboard"),
|
||||||
|
|
||||||
# Home (für Kompatibilität mit bestehenden Templates)
|
# Home (für Kompatibilität mit bestehenden Templates)
|
||||||
path('home/', views.home, name='home'),
|
path("home/", views.home, name="home"),
|
||||||
|
|
||||||
# CSV Import URLs
|
# CSV Import URLs
|
||||||
path('import/', views.csv_import_list, name='csv_import_list'),
|
path("import/", views.csv_import_list, name="csv_import_list"),
|
||||||
path('import/neu/', views.csv_import_create, name='csv_import_create'),
|
path("import/neu/", views.csv_import_create, name="csv_import_create"),
|
||||||
|
|
||||||
# Destinatär URLs (Förderungsempfänger)
|
# Destinatär URLs (Förderungsempfänger)
|
||||||
path('destinataere/', views.destinataer_list, name='destinataer_list'),
|
path("destinataere/", views.destinataer_list, name="destinataer_list"),
|
||||||
path('destinataere/<uuid:pk>/', views.destinataer_detail, name='destinataer_detail'),
|
path(
|
||||||
path('destinataere/neu/', views.destinataer_create, name='destinataer_create'),
|
"destinataere/<uuid:pk>/", views.destinataer_detail, name="destinataer_detail"
|
||||||
path('destinataere/<uuid:pk>/bearbeiten/', views.destinataer_update, name='destinataer_update'),
|
),
|
||||||
path('destinataere/<uuid:pk>/loeschen/', views.destinataer_delete, name='destinataer_delete'),
|
path("destinataere/neu/", views.destinataer_create, name="destinataer_create"),
|
||||||
path('destinataere/<uuid:pk>/notiz/', views.destinataer_notiz_create, name='destinataer_notiz_create'),
|
path(
|
||||||
path('destinataere/<uuid:pk>/export/', views.destinataer_export, name='destinataer_export'),
|
"destinataere/<uuid:pk>/bearbeiten/",
|
||||||
|
views.destinataer_update,
|
||||||
|
name="destinataer_update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"destinataere/<uuid:pk>/loeschen/",
|
||||||
|
views.destinataer_delete,
|
||||||
|
name="destinataer_delete",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"destinataere/<uuid:pk>/notiz/",
|
||||||
|
views.destinataer_notiz_create,
|
||||||
|
name="destinataer_notiz_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"destinataere/<uuid:pk>/export/",
|
||||||
|
views.destinataer_export,
|
||||||
|
name="destinataer_export",
|
||||||
|
),
|
||||||
# Paechter URLs (Landpächter)
|
# Paechter URLs (Landpächter)
|
||||||
path('paechter/', views.paechter_list, name='paechter_list'),
|
path("paechter/", views.paechter_list, name="paechter_list"),
|
||||||
path('paechter/<uuid:pk>/', views.paechter_detail, name='paechter_detail'),
|
path("paechter/<uuid:pk>/", views.paechter_detail, name="paechter_detail"),
|
||||||
path('paechter/neu/', views.paechter_create, name='paechter_create'),
|
path("paechter/neu/", views.paechter_create, name="paechter_create"),
|
||||||
path('paechter/<uuid:pk>/bearbeiten/', views.paechter_update, name='paechter_update'),
|
path(
|
||||||
path('paechter/<uuid:pk>/loeschen/', views.paechter_delete, name='paechter_delete'),
|
"paechter/<uuid:pk>/bearbeiten/", views.paechter_update, name="paechter_update"
|
||||||
path('paechter/<uuid:pk>/export/', views.paechter_export, name='paechter_export'),
|
),
|
||||||
|
path("paechter/<uuid:pk>/loeschen/", views.paechter_delete, name="paechter_delete"),
|
||||||
|
path("paechter/<uuid:pk>/export/", views.paechter_export, name="paechter_export"),
|
||||||
# Legacy Person URLs removed (Destinatäre ersetzen Personen)
|
# Legacy Person URLs removed (Destinatäre ersetzen Personen)
|
||||||
|
|
||||||
# Land URLs
|
# Land URLs
|
||||||
path('laendereien/', views.land_list, name='land_list'),
|
path("laendereien/", views.land_list, name="land_list"),
|
||||||
path('laendereien/<uuid:pk>/', views.land_detail, name='land_detail'),
|
path("laendereien/<uuid:pk>/", views.land_detail, name="land_detail"),
|
||||||
path('laendereien/neu/', views.land_create, name='land_create'),
|
path("laendereien/neu/", views.land_create, name="land_create"),
|
||||||
path('laendereien/<uuid:pk>/bearbeiten/', views.land_update, name='land_update'),
|
path("laendereien/<uuid:pk>/bearbeiten/", views.land_update, name="land_update"),
|
||||||
path('laendereien/<uuid:pk>/loeschen/', views.land_delete, name='land_delete'),
|
path("laendereien/<uuid:pk>/loeschen/", views.land_delete, name="land_delete"),
|
||||||
path('laendereien/<uuid:pk>/export/', views.land_export, name='land_export'),
|
path("laendereien/<uuid:pk>/export/", views.land_export, name="land_export"),
|
||||||
|
|
||||||
# Landabrechnung URLs
|
# Landabrechnung URLs
|
||||||
path('landabrechnungen/', views.land_abrechnung_list, name='land_abrechnung_list'),
|
path("landabrechnungen/", views.land_abrechnung_list, name="land_abrechnung_list"),
|
||||||
path('landabrechnungen/<uuid:pk>/', views.land_abrechnung_detail, name='land_abrechnung_detail'),
|
path(
|
||||||
path('landabrechnungen/neu/', views.land_abrechnung_create, name='land_abrechnung_create'),
|
"landabrechnungen/<uuid:pk>/",
|
||||||
path('landabrechnungen/<uuid:pk>/bearbeiten/', views.land_abrechnung_update, name='land_abrechnung_update'),
|
views.land_abrechnung_detail,
|
||||||
path('landabrechnungen/<uuid:pk>/loeschen/', views.land_abrechnung_delete, name='land_abrechnung_delete'),
|
name="land_abrechnung_detail",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"landabrechnungen/neu/",
|
||||||
|
views.land_abrechnung_create,
|
||||||
|
name="land_abrechnung_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"landabrechnungen/<uuid:pk>/bearbeiten/",
|
||||||
|
views.land_abrechnung_update,
|
||||||
|
name="land_abrechnung_update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"landabrechnungen/<uuid:pk>/loeschen/",
|
||||||
|
views.land_abrechnung_delete,
|
||||||
|
name="land_abrechnung_delete",
|
||||||
|
),
|
||||||
# Vereinheitlichte Verpachtung URLs (direkt im Land)
|
# Vereinheitlichte Verpachtung URLs (direkt im Land)
|
||||||
path('laendereien/<uuid:land_pk>/verpachtung/neu/', views.land_verpachtung_create, name='land_verpachtung_create'),
|
path(
|
||||||
path('laendereien/<uuid:land_pk>/verpachtung/bearbeiten/', views.land_verpachtung_edit, name='land_verpachtung_edit'),
|
"laendereien/<uuid:land_pk>/verpachtung/neu/",
|
||||||
path('laendereien/<uuid:land_pk>/verpachtung/beenden/', views.land_verpachtung_end, name='land_verpachtung_end'),
|
views.land_verpachtung_create,
|
||||||
|
name="land_verpachtung_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"laendereien/<uuid:land_pk>/verpachtung/bearbeiten/",
|
||||||
|
views.land_verpachtung_edit,
|
||||||
|
name="land_verpachtung_edit",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"laendereien/<uuid:land_pk>/verpachtung/beenden/",
|
||||||
|
views.land_verpachtung_end,
|
||||||
|
name="land_verpachtung_end",
|
||||||
|
),
|
||||||
# LandVerpachtung URLs (neue Verpachtungen)
|
# LandVerpachtung URLs (neue Verpachtungen)
|
||||||
path('laendereien/verpachtungen/<uuid:pk>/', views.land_verpachtung_detail, name='land_verpachtung_detail'),
|
path(
|
||||||
path('laendereien/verpachtungen/<uuid:pk>/bearbeiten/', views.land_verpachtung_update, name='land_verpachtung_update'),
|
"laendereien/verpachtungen/<uuid:pk>/",
|
||||||
path('laendereien/verpachtungen/<uuid:pk>/beenden/', views.land_verpachtung_end_direct, name='land_verpachtung_end_direct'),
|
views.land_verpachtung_detail,
|
||||||
|
name="land_verpachtung_detail",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"laendereien/verpachtungen/<uuid:pk>/bearbeiten/",
|
||||||
|
views.land_verpachtung_update,
|
||||||
|
name="land_verpachtung_update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"laendereien/verpachtungen/<uuid:pk>/beenden/",
|
||||||
|
views.land_verpachtung_end_direct,
|
||||||
|
name="land_verpachtung_end_direct",
|
||||||
|
),
|
||||||
# Förderung URLs
|
# Förderung URLs
|
||||||
path('foerderungen/', views.foerderung_list, name='foerderung_list'),
|
path("foerderungen/", views.foerderung_list, name="foerderung_list"),
|
||||||
path('foerderungen/<uuid:pk>/', views.foerderung_detail, name='foerderung_detail'),
|
path("foerderungen/<uuid:pk>/", views.foerderung_detail, name="foerderung_detail"),
|
||||||
path('foerderungen/neu/', views.foerderung_create, name='foerderung_create'),
|
path("foerderungen/neu/", views.foerderung_create, name="foerderung_create"),
|
||||||
path('foerderungen/<uuid:pk>/bearbeiten/', views.foerderung_update, name='foerderung_update'),
|
path(
|
||||||
path('foerderungen/<uuid:pk>/loeschen/', views.foerderung_delete, name='foerderung_delete'),
|
"foerderungen/<uuid:pk>/bearbeiten/",
|
||||||
|
views.foerderung_update,
|
||||||
|
name="foerderung_update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"foerderungen/<uuid:pk>/loeschen/",
|
||||||
|
views.foerderung_delete,
|
||||||
|
name="foerderung_delete",
|
||||||
|
),
|
||||||
# Dokumente URLs
|
# Dokumente URLs
|
||||||
path('dokumente/', views.dokument_list, name='dokument_list'),
|
path("dokumente/", views.dokument_list, name="dokument_list"),
|
||||||
path('dokumente/<uuid:pk>/', views.dokument_detail, name='dokument_detail'),
|
path("dokumente/<uuid:pk>/", views.dokument_detail, name="dokument_detail"),
|
||||||
path('dokumente/neu/', views.dokument_create, name='dokument_create'),
|
path("dokumente/neu/", views.dokument_create, name="dokument_create"),
|
||||||
path('dokumente/<uuid:pk>/bearbeiten/', views.dokument_update, name='dokument_update'),
|
path(
|
||||||
path('dokumente/<uuid:pk>/loeschen/', views.dokument_delete, name='dokument_delete'),
|
"dokumente/<uuid:pk>/bearbeiten/", views.dokument_update, name="dokument_update"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"dokumente/<uuid:pk>/loeschen/", views.dokument_delete, name="dokument_delete"
|
||||||
|
),
|
||||||
# Dokumentenverwaltung (Paperless-Integration, Verwaltung & Verknüpfung)
|
# Dokumentenverwaltung (Paperless-Integration, Verwaltung & Verknüpfung)
|
||||||
path('dokumente/verwaltung/', views.dokument_management, name='dokument_management'),
|
path(
|
||||||
|
"dokumente/verwaltung/", views.dokument_management, name="dokument_management"
|
||||||
|
),
|
||||||
# Legacy document URLs removed - use dokument_management instead
|
# Legacy document URLs removed - use dokument_management instead
|
||||||
|
|
||||||
# Dokument-Verknüpfung
|
# Dokument-Verknüpfung
|
||||||
path('api/link-document/search/', views.link_document_search, name='link_document_search'),
|
path(
|
||||||
path('api/link-document/create/', views.link_document_create, name='link_document_create'),
|
"api/link-document/search/",
|
||||||
path('api/link-document/list/', views.link_document_list, name='link_document_list'),
|
views.link_document_search,
|
||||||
path('api/link-document/update/', views.link_document_update, name='link_document_update'),
|
name="link_document_search",
|
||||||
path('api/link-document/delete/<uuid:link_id>/', views.link_document_delete, name='link_document_delete'),
|
),
|
||||||
|
path(
|
||||||
|
"api/link-document/create/",
|
||||||
|
views.link_document_create,
|
||||||
|
name="link_document_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"api/link-document/list/", views.link_document_list, name="link_document_list"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"api/link-document/update/",
|
||||||
|
views.link_document_update,
|
||||||
|
name="link_document_update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"api/link-document/delete/<uuid:link_id>/",
|
||||||
|
views.link_document_delete,
|
||||||
|
name="link_document_delete",
|
||||||
|
),
|
||||||
# Legacy dokument_verknuepfung URL removed - use dokument_management instead
|
# Legacy dokument_verknuepfung URL removed - use dokument_management instead
|
||||||
|
|
||||||
# Jahresbericht URLs
|
# Jahresbericht URLs
|
||||||
path('berichte/', views.bericht_list, name='bericht_list'),
|
path("berichte/", views.bericht_list, name="bericht_list"),
|
||||||
path('berichte/jahresbericht/', views.jahresbericht_generate_redirect, name='jahresbericht_generate_redirect'),
|
path(
|
||||||
path('berichte/jahresbericht/<int:jahr>/', views.jahresbericht_generate, name='jahresbericht_generate'),
|
"berichte/jahresbericht/",
|
||||||
path('berichte/jahresbericht/<int:jahr>/pdf/', views.jahresbericht_pdf, name='jahresbericht_pdf'),
|
views.jahresbericht_generate_redirect,
|
||||||
|
name="jahresbericht_generate_redirect",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"berichte/jahresbericht/<int:jahr>/",
|
||||||
|
views.jahresbericht_generate,
|
||||||
|
name="jahresbericht_generate",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"berichte/jahresbericht/<int:jahr>/pdf/",
|
||||||
|
views.jahresbericht_pdf,
|
||||||
|
name="jahresbericht_pdf",
|
||||||
|
),
|
||||||
# Geschäftsführung URLs
|
# Geschäftsführung URLs
|
||||||
path('geschaeftsfuehrung/', views.geschaeftsfuehrung, name='geschaeftsfuehrung'),
|
path("geschaeftsfuehrung/", views.geschaeftsfuehrung, name="geschaeftsfuehrung"),
|
||||||
path('geschaeftsfuehrung/konten/', views.konto_list, name='konto_list'),
|
path("geschaeftsfuehrung/konten/", views.konto_list, name="konto_list"),
|
||||||
path('geschaeftsfuehrung/konten/neu/', views.konto_create, name='konto_create'),
|
path("geschaeftsfuehrung/konten/neu/", views.konto_create, name="konto_create"),
|
||||||
path('geschaeftsfuehrung/konten/<uuid:pk>/', views.konto_detail, name='konto_detail'),
|
path(
|
||||||
path('geschaeftsfuehrung/konten/<uuid:pk>/bearbeiten/', views.konto_edit, name='konto_edit'),
|
"geschaeftsfuehrung/konten/<uuid:pk>/", views.konto_detail, name="konto_detail"
|
||||||
path('geschaeftsfuehrung/verwaltungskosten/', views.verwaltungskosten_list, name='verwaltungskosten_list'),
|
),
|
||||||
path('geschaeftsfuehrung/verwaltungskosten/neu/', views.verwaltungskosten_create, name='verwaltungskosten_create'),
|
path(
|
||||||
path('geschaeftsfuehrung/verwaltungskosten/<uuid:pk>/bearbeiten/', views.verwaltungskosten_edit, name='verwaltungskosten_edit'),
|
"geschaeftsfuehrung/konten/<uuid:pk>/bearbeiten/",
|
||||||
path('verwaltungskosten/mark-paid/', views.mark_expense_paid, name='mark_expense_paid'),
|
views.konto_edit,
|
||||||
path('geschaeftsfuehrung/rentmeister/', views.rentmeister_list, name='rentmeister_list'),
|
name="konto_edit",
|
||||||
path('geschaeftsfuehrung/rentmeister/neu/', views.rentmeister_create, name='rentmeister_create'),
|
),
|
||||||
path('geschaeftsfuehrung/rentmeister/<uuid:pk>/', views.rentmeister_detail, name='rentmeister_detail'),
|
path(
|
||||||
path('geschaeftsfuehrung/rentmeister/<uuid:pk>/bearbeiten/', views.rentmeister_edit, name='rentmeister_edit'),
|
"geschaeftsfuehrung/verwaltungskosten/",
|
||||||
path('geschaeftsfuehrung/rentmeister/<uuid:pk>/ausgaben/', views.rentmeister_ausgaben, name='rentmeister_ausgaben'),
|
views.verwaltungskosten_list,
|
||||||
|
name="verwaltungskosten_list",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"geschaeftsfuehrung/verwaltungskosten/neu/",
|
||||||
|
views.verwaltungskosten_create,
|
||||||
|
name="verwaltungskosten_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"geschaeftsfuehrung/verwaltungskosten/<uuid:pk>/bearbeiten/",
|
||||||
|
views.verwaltungskosten_edit,
|
||||||
|
name="verwaltungskosten_edit",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"verwaltungskosten/mark-paid/",
|
||||||
|
views.mark_expense_paid,
|
||||||
|
name="mark_expense_paid",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"geschaeftsfuehrung/rentmeister/",
|
||||||
|
views.rentmeister_list,
|
||||||
|
name="rentmeister_list",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"geschaeftsfuehrung/rentmeister/neu/",
|
||||||
|
views.rentmeister_create,
|
||||||
|
name="rentmeister_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"geschaeftsfuehrung/rentmeister/<uuid:pk>/",
|
||||||
|
views.rentmeister_detail,
|
||||||
|
name="rentmeister_detail",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"geschaeftsfuehrung/rentmeister/<uuid:pk>/bearbeiten/",
|
||||||
|
views.rentmeister_edit,
|
||||||
|
name="rentmeister_edit",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"geschaeftsfuehrung/rentmeister/<uuid:pk>/ausgaben/",
|
||||||
|
views.rentmeister_ausgaben,
|
||||||
|
name="rentmeister_ausgaben",
|
||||||
|
),
|
||||||
# Administration URLs
|
# Administration URLs
|
||||||
path('administration/', views.administration, name='administration'),
|
path("administration/", views.administration, name="administration"),
|
||||||
path('administration/settings/', views.app_settings, name='app_settings'),
|
path("administration/settings/", views.app_settings, name="app_settings"),
|
||||||
path('administration/audit-log/', views.audit_log_list, name='audit_log_list'),
|
path("administration/audit-log/", views.audit_log_list, name="audit_log_list"),
|
||||||
path('administration/backup/', views.backup_management, name='backup_management'),
|
path("administration/backup/", views.backup_management, name="backup_management"),
|
||||||
path('administration/backup/<uuid:backup_id>/download/', views.backup_download, name='backup_download'),
|
path(
|
||||||
path('administration/backup/restore/', views.backup_restore, name='backup_restore'),
|
"administration/backup/<uuid:backup_id>/download/",
|
||||||
path('administration/unterstuetzungen/', views.unterstuetzungen_list, name='unterstuetzungen_list'),
|
views.backup_download,
|
||||||
path('administration/unterstuetzungen/<uuid:pk>/bearbeiten/', views.unterstuetzung_edit, name='unterstuetzung_edit'),
|
name="backup_download",
|
||||||
path('administration/unterstuetzungen/<uuid:pk>/loeschen/', views.unterstuetzung_delete, name='unterstuetzung_delete'),
|
),
|
||||||
|
path("administration/backup/restore/", views.backup_restore, name="backup_restore"),
|
||||||
|
path(
|
||||||
|
"administration/unterstuetzungen/",
|
||||||
|
views.unterstuetzungen_list,
|
||||||
|
name="unterstuetzungen_list",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"administration/unterstuetzungen/<uuid:pk>/bearbeiten/",
|
||||||
|
views.unterstuetzung_edit,
|
||||||
|
name="unterstuetzung_edit",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"administration/unterstuetzungen/<uuid:pk>/loeschen/",
|
||||||
|
views.unterstuetzung_delete,
|
||||||
|
name="unterstuetzung_delete",
|
||||||
|
),
|
||||||
# Unterstützungen URLs (direct access from Destinataer)
|
# Unterstützungen URLs (direct access from Destinataer)
|
||||||
path('unterstuetzungen/', views.unterstuetzungen_all, name='unterstuetzungen_all'),
|
path("unterstuetzungen/", views.unterstuetzungen_all, name="unterstuetzungen_all"),
|
||||||
path('unterstuetzungen/neu/', views.unterstuetzung_create, name='unterstuetzung_create'),
|
path(
|
||||||
path('unterstuetzungen/<uuid:pk>/', views.unterstuetzung_detail, name='unterstuetzung_detail'),
|
"unterstuetzungen/neu/",
|
||||||
path('unterstuetzungen/<uuid:pk>/bezahlt/', views.unterstuetzung_mark_paid, name='unterstuetzung_mark_paid'),
|
views.unterstuetzung_create,
|
||||||
path('unterstuetzungen/wiederkehrend/', views.wiederkehrende_unterstuetzungen, name='wiederkehrende_unterstuetzungen'),
|
name="unterstuetzung_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"unterstuetzungen/<uuid:pk>/",
|
||||||
|
views.unterstuetzung_detail,
|
||||||
|
name="unterstuetzung_detail",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"unterstuetzungen/<uuid:pk>/bezahlt/",
|
||||||
|
views.unterstuetzung_mark_paid,
|
||||||
|
name="unterstuetzung_mark_paid",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"unterstuetzungen/wiederkehrend/",
|
||||||
|
views.wiederkehrende_unterstuetzungen,
|
||||||
|
name="wiederkehrende_unterstuetzungen",
|
||||||
|
),
|
||||||
# AJAX endpoints
|
# AJAX endpoints
|
||||||
path('api/destinataer/<uuid:destinataer_id>/info/', views.get_destinataer_info, name='get_destinataer_info'),
|
path(
|
||||||
|
"api/destinataer/<uuid:destinataer_id>/info/",
|
||||||
|
views.get_destinataer_info,
|
||||||
|
name="get_destinataer_info",
|
||||||
|
),
|
||||||
# Authentication URLs
|
# Authentication URLs
|
||||||
path('login/', views.user_login, name='login'),
|
path("login/", views.user_login, name="login"),
|
||||||
path('logout/', views.user_logout, name='logout'),
|
path("logout/", views.user_logout, name="logout"),
|
||||||
|
|
||||||
# User Management URLs
|
# User Management URLs
|
||||||
path('administration/users/', views.user_management, name='user_management'),
|
path("administration/users/", views.user_management, name="user_management"),
|
||||||
path('administration/users/create/', views.user_create, name='user_create'),
|
path("administration/users/create/", views.user_create, name="user_create"),
|
||||||
path('administration/users/<int:pk>/', views.user_detail, name='user_detail'),
|
path("administration/users/<int:pk>/", views.user_detail, name="user_detail"),
|
||||||
path('administration/users/<int:pk>/edit/', views.user_edit, name='user_edit'),
|
path("administration/users/<int:pk>/edit/", views.user_edit, name="user_edit"),
|
||||||
path('administration/users/<int:pk>/password/', views.user_change_password, name='user_change_password'),
|
path(
|
||||||
path('administration/users/<int:pk>/permissions/', views.user_permissions, name='user_permissions'),
|
"administration/users/<int:pk>/password/",
|
||||||
path('administration/users/<int:pk>/delete/', views.user_delete, name='user_delete'),
|
views.user_change_password,
|
||||||
|
name="user_change_password",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"administration/users/<int:pk>/permissions/",
|
||||||
|
views.user_permissions,
|
||||||
|
name="user_permissions",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"administration/users/<int:pk>/delete/", views.user_delete, name="user_delete"
|
||||||
|
),
|
||||||
# Hilfsbox URLs
|
# Hilfsbox URLs
|
||||||
path('help-box/edit/', views.edit_help_box, name='edit_help_box'),
|
path("help-box/edit/", views.edit_help_box, name="edit_help_box"),
|
||||||
path('help-box/admin/', views.edit_help_box, name='help_boxes_admin'),
|
path("help-box/admin/", views.edit_help_box, name="help_boxes_admin"),
|
||||||
|
|
||||||
# API URLs
|
# API URLs
|
||||||
path('api/land-stats/', views.land_stats_api, name='land_stats_api'),
|
path("api/land-stats/", views.land_stats_api, name="land_stats_api"),
|
||||||
path('api/health/', views.health_check, name='health_check'),
|
path("api/health/", views.health_check, name="health_check"),
|
||||||
path('api/paperless/ping/', views.paperless_ping, name='paperless_ping'),
|
path("api/paperless/ping/", views.paperless_ping, name="paperless_ping"),
|
||||||
path('api/paperless/documents/', views.paperless_documents, name='paperless_documents'),
|
path(
|
||||||
path('api/paperless/tags/', views.paperless_tags_only, name='paperless_tags_only'),
|
"api/paperless/documents/",
|
||||||
path('api/paperless/debug/', views.paperless_debug, name='paperless_debug'),
|
views.paperless_documents,
|
||||||
path('api/paperless/documents/<int:doc_id>/', views.paperless_document_redirect, name='paperless_document_redirect'),
|
name="paperless_documents",
|
||||||
|
),
|
||||||
|
path("api/paperless/tags/", views.paperless_tags_only, name="paperless_tags_only"),
|
||||||
|
path("api/paperless/debug/", views.paperless_debug, name="paperless_debug"),
|
||||||
|
path(
|
||||||
|
"api/paperless/documents/<int:doc_id>/",
|
||||||
|
views.paperless_document_redirect,
|
||||||
|
name="paperless_document_redirect",
|
||||||
|
),
|
||||||
# Gramps integration (probe)
|
# Gramps integration (probe)
|
||||||
path('api/gramps/search/', views.gramps_search_api, name='gramps_search_api'),
|
path("api/gramps/search/", views.gramps_search_api, name="gramps_search_api"),
|
||||||
path('api/gramps/debug/', views.gramps_debug_api, name='gramps_debug_api'),
|
path("api/gramps/debug/", views.gramps_debug_api, name="gramps_debug_api"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,61 +1,65 @@
|
|||||||
"""
|
"""
|
||||||
Configuration utilities for the Stiftung application
|
Configuration utilities for the Stiftung application
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from stiftung.models import AppConfiguration
|
from stiftung.models import AppConfiguration
|
||||||
|
|
||||||
|
|
||||||
def get_config(key, default=None, fallback_to_settings=True):
|
def get_config(key, default=None, fallback_to_settings=True):
|
||||||
"""
|
"""
|
||||||
Get a configuration value from the database or fall back to Django settings
|
Get a configuration value from the database or fall back to Django settings
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: The configuration key
|
key: The configuration key
|
||||||
default: Default value if not found
|
default: Default value if not found
|
||||||
fallback_to_settings: If True, try to get from Django settings using the key in uppercase
|
fallback_to_settings: If True, try to get from Django settings using the key in uppercase
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The configuration value
|
The configuration value
|
||||||
"""
|
"""
|
||||||
# Try to get from AppConfiguration first
|
# Try to get from AppConfiguration first
|
||||||
value = AppConfiguration.get_setting(key, None)
|
value = AppConfiguration.get_setting(key, None)
|
||||||
|
|
||||||
# Fall back to Django settings if value is None or empty string
|
# Fall back to Django settings if value is None or empty string
|
||||||
if not value and fallback_to_settings:
|
if not value and fallback_to_settings:
|
||||||
settings_key = key.upper()
|
settings_key = key.upper()
|
||||||
return getattr(settings, settings_key, default)
|
return getattr(settings, settings_key, default)
|
||||||
|
|
||||||
return value if value is not None else default
|
return value if value is not None else default
|
||||||
|
|
||||||
|
|
||||||
def get_paperless_config():
|
def get_paperless_config():
|
||||||
"""
|
"""
|
||||||
Get all Paperless-related configuration values
|
Get all Paperless-related configuration values
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Dictionary containing all Paperless configuration
|
dict: Dictionary containing all Paperless configuration
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
'api_url': get_config('paperless_api_url', 'http://192.168.178.167:30070'),
|
"api_url": get_config("paperless_api_url", "http://192.168.178.167:30070"),
|
||||||
'api_token': get_config('paperless_api_token', ''),
|
"api_token": get_config("paperless_api_token", ""),
|
||||||
'destinataere_tag': get_config('paperless_destinataere_tag', 'Stiftung_Destinatäre'),
|
"destinataere_tag": get_config(
|
||||||
'destinataere_tag_id': get_config('paperless_destinataere_tag_id', '210'),
|
"paperless_destinataere_tag", "Stiftung_Destinatäre"
|
||||||
'land_tag': get_config('paperless_land_tag', 'Stiftung_Land_und_Pächter'),
|
),
|
||||||
'land_tag_id': get_config('paperless_land_tag_id', '204'),
|
"destinataere_tag_id": get_config("paperless_destinataere_tag_id", "210"),
|
||||||
'admin_tag': get_config('paperless_admin_tag', 'Stiftung_Administration'),
|
"land_tag": get_config("paperless_land_tag", "Stiftung_Land_und_Pächter"),
|
||||||
'admin_tag_id': get_config('paperless_admin_tag_id', '216'),
|
"land_tag_id": get_config("paperless_land_tag_id", "204"),
|
||||||
|
"admin_tag": get_config("paperless_admin_tag", "Stiftung_Administration"),
|
||||||
|
"admin_tag_id": get_config("paperless_admin_tag_id", "216"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def set_config(key, value, **kwargs):
|
def set_config(key, value, **kwargs):
|
||||||
"""
|
"""
|
||||||
Set a configuration value
|
Set a configuration value
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: The configuration key
|
key: The configuration key
|
||||||
value: The value to set
|
value: The value to set
|
||||||
**kwargs: Additional parameters for AppConfiguration.set_setting
|
**kwargs: Additional parameters for AppConfiguration.set_setting
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
AppConfiguration: The configuration object
|
AppConfiguration: The configuration object
|
||||||
"""
|
"""
|
||||||
@@ -65,9 +69,9 @@ def set_config(key, value, **kwargs):
|
|||||||
def is_paperless_configured():
|
def is_paperless_configured():
|
||||||
"""
|
"""
|
||||||
Check if Paperless is properly configured
|
Check if Paperless is properly configured
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if API URL and token are configured
|
bool: True if API URL and token are configured
|
||||||
"""
|
"""
|
||||||
config = get_paperless_config()
|
config = get_paperless_config()
|
||||||
return bool(config['api_url'] and config['api_token'])
|
return bool(config["api_url"] and config["api_token"])
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
"""
|
"""
|
||||||
PDF generation utilities with corporate identity support
|
PDF generation utilities with corporate identity support
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import base64
|
import base64
|
||||||
|
import os
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template.loader import render_to_string
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.template.loader import render_to_string
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
# Try to import WeasyPrint, fall back gracefully if not available
|
# Try to import WeasyPrint, fall back gracefully if not available
|
||||||
try:
|
try:
|
||||||
from weasyprint import HTML, CSS
|
from weasyprint import CSS, HTML
|
||||||
from weasyprint.text.fonts import FontConfiguration
|
from weasyprint.text.fonts import FontConfiguration
|
||||||
|
|
||||||
WEASYPRINT_AVAILABLE = True
|
WEASYPRINT_AVAILABLE = True
|
||||||
IMPORT_ERROR = None
|
IMPORT_ERROR = None
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
@@ -35,72 +38,84 @@ from stiftung.models import AppConfiguration
|
|||||||
|
|
||||||
class PDFGenerator:
|
class PDFGenerator:
|
||||||
"""Corporate identity PDF generator"""
|
"""Corporate identity PDF generator"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if WEASYPRINT_AVAILABLE:
|
if WEASYPRINT_AVAILABLE:
|
||||||
self.font_config = FontConfiguration()
|
self.font_config = FontConfiguration()
|
||||||
else:
|
else:
|
||||||
self.font_config = None
|
self.font_config = None
|
||||||
|
|
||||||
def is_available(self):
|
def is_available(self):
|
||||||
"""Check if PDF generation is available"""
|
"""Check if PDF generation is available"""
|
||||||
return WEASYPRINT_AVAILABLE
|
return WEASYPRINT_AVAILABLE
|
||||||
|
|
||||||
def get_corporate_settings(self):
|
def get_corporate_settings(self):
|
||||||
"""Get corporate identity settings from configuration"""
|
"""Get corporate identity settings from configuration"""
|
||||||
return {
|
return {
|
||||||
'stiftung_name': AppConfiguration.get_setting('corporate_stiftung_name', 'Stiftung'),
|
"stiftung_name": AppConfiguration.get_setting(
|
||||||
'logo_path': AppConfiguration.get_setting('corporate_logo_path', ''),
|
"corporate_stiftung_name", "Stiftung"
|
||||||
'primary_color': AppConfiguration.get_setting('corporate_primary_color', '#2c3e50'),
|
),
|
||||||
'secondary_color': AppConfiguration.get_setting('corporate_secondary_color', '#3498db'),
|
"logo_path": AppConfiguration.get_setting("corporate_logo_path", ""),
|
||||||
'address_line1': AppConfiguration.get_setting('corporate_address_line1', ''),
|
"primary_color": AppConfiguration.get_setting(
|
||||||
'address_line2': AppConfiguration.get_setting('corporate_address_line2', ''),
|
"corporate_primary_color", "#2c3e50"
|
||||||
'phone': AppConfiguration.get_setting('corporate_phone', ''),
|
),
|
||||||
'email': AppConfiguration.get_setting('corporate_email', ''),
|
"secondary_color": AppConfiguration.get_setting(
|
||||||
'website': AppConfiguration.get_setting('corporate_website', ''),
|
"corporate_secondary_color", "#3498db"
|
||||||
'footer_text': AppConfiguration.get_setting('corporate_footer_text', 'Dieser Bericht wurde automatisch generiert.'),
|
),
|
||||||
|
"address_line1": AppConfiguration.get_setting(
|
||||||
|
"corporate_address_line1", ""
|
||||||
|
),
|
||||||
|
"address_line2": AppConfiguration.get_setting(
|
||||||
|
"corporate_address_line2", ""
|
||||||
|
),
|
||||||
|
"phone": AppConfiguration.get_setting("corporate_phone", ""),
|
||||||
|
"email": AppConfiguration.get_setting("corporate_email", ""),
|
||||||
|
"website": AppConfiguration.get_setting("corporate_website", ""),
|
||||||
|
"footer_text": AppConfiguration.get_setting(
|
||||||
|
"corporate_footer_text", "Dieser Bericht wurde automatisch generiert."
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_logo_base64(self, logo_path):
|
def get_logo_base64(self, logo_path):
|
||||||
"""Convert logo to base64 for embedding in PDF"""
|
"""Convert logo to base64 for embedding in PDF"""
|
||||||
if not logo_path:
|
if not logo_path:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Try different possible paths
|
# Try different possible paths
|
||||||
possible_paths = [
|
possible_paths = [
|
||||||
logo_path,
|
logo_path,
|
||||||
os.path.join(settings.MEDIA_ROOT, logo_path),
|
os.path.join(settings.MEDIA_ROOT, logo_path),
|
||||||
os.path.join(settings.STATIC_ROOT or '', logo_path),
|
os.path.join(settings.STATIC_ROOT or "", logo_path),
|
||||||
os.path.join(settings.BASE_DIR, 'static', logo_path),
|
os.path.join(settings.BASE_DIR, "static", logo_path),
|
||||||
]
|
]
|
||||||
|
|
||||||
for path in possible_paths:
|
for path in possible_paths:
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
try:
|
try:
|
||||||
with open(path, 'rb') as img_file:
|
with open(path, "rb") as img_file:
|
||||||
img_data = base64.b64encode(img_file.read()).decode('utf-8')
|
img_data = base64.b64encode(img_file.read()).decode("utf-8")
|
||||||
# Determine MIME type
|
# Determine MIME type
|
||||||
ext = os.path.splitext(path)[1].lower()
|
ext = os.path.splitext(path)[1].lower()
|
||||||
if ext in ['.jpg', '.jpeg']:
|
if ext in [".jpg", ".jpeg"]:
|
||||||
mime_type = 'image/jpeg'
|
mime_type = "image/jpeg"
|
||||||
elif ext == '.png':
|
elif ext == ".png":
|
||||||
mime_type = 'image/png'
|
mime_type = "image/png"
|
||||||
elif ext == '.svg':
|
elif ext == ".svg":
|
||||||
mime_type = 'image/svg+xml'
|
mime_type = "image/svg+xml"
|
||||||
else:
|
else:
|
||||||
mime_type = 'image/png' # default
|
mime_type = "image/png" # default
|
||||||
|
|
||||||
return f"data:{mime_type};base64,{img_data}"
|
return f"data:{mime_type};base64,{img_data}"
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_base_css(self, corporate_settings):
|
def get_base_css(self, corporate_settings):
|
||||||
"""Generate base CSS for corporate identity"""
|
"""Generate base CSS for corporate identity"""
|
||||||
primary_color = corporate_settings.get('primary_color', '#2c3e50')
|
primary_color = corporate_settings.get("primary_color", "#2c3e50")
|
||||||
secondary_color = corporate_settings.get('secondary_color', '#3498db')
|
secondary_color = corporate_settings.get("secondary_color", "#3498db")
|
||||||
|
|
||||||
return f"""
|
return f"""
|
||||||
@page {{
|
@page {{
|
||||||
size: A4;
|
size: A4;
|
||||||
@@ -291,7 +306,7 @@ class PDFGenerator:
|
|||||||
page-break-before: always;
|
page-break-before: always;
|
||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def generate_pdf_response(self, html_content, filename, css_content=None):
|
def generate_pdf_response(self, html_content, filename, css_content=None):
|
||||||
"""Generate PDF response from HTML content"""
|
"""Generate PDF response from HTML content"""
|
||||||
if not WEASYPRINT_AVAILABLE:
|
if not WEASYPRINT_AVAILABLE:
|
||||||
@@ -320,27 +335,30 @@ class PDFGenerator:
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
response = HttpResponse(error_html, content_type='text/html')
|
response = HttpResponse(error_html, content_type="text/html")
|
||||||
response['Content-Disposition'] = f'inline; filename="{filename.replace(".pdf", "_preview.html")}"'
|
response["Content-Disposition"] = (
|
||||||
|
f'inline; filename="{filename.replace(".pdf", "_preview.html")}"'
|
||||||
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Create CSS string
|
# Create CSS string
|
||||||
if css_content:
|
if css_content:
|
||||||
css = CSS(string=css_content, font_config=self.font_config)
|
css = CSS(string=css_content, font_config=self.font_config)
|
||||||
else:
|
else:
|
||||||
css = None
|
css = None
|
||||||
|
|
||||||
# Generate PDF
|
# Generate PDF
|
||||||
html_doc = HTML(string=html_content)
|
html_doc = HTML(string=html_content)
|
||||||
pdf_bytes = html_doc.write_pdf(stylesheets=[css] if css else None,
|
pdf_bytes = html_doc.write_pdf(
|
||||||
font_config=self.font_config)
|
stylesheets=[css] if css else None, font_config=self.font_config
|
||||||
|
)
|
||||||
|
|
||||||
# Create response
|
# Create response
|
||||||
response = HttpResponse(pdf_bytes, content_type='application/pdf')
|
response = HttpResponse(pdf_bytes, content_type="application/pdf")
|
||||||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
||||||
return response
|
return response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Fallback: return error message as HTML
|
# Fallback: return error message as HTML
|
||||||
error_html = f"""
|
error_html = f"""
|
||||||
@@ -368,15 +386,19 @@ class PDFGenerator:
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
response = HttpResponse(error_html, content_type='text/html')
|
response = HttpResponse(error_html, content_type="text/html")
|
||||||
response['Content-Disposition'] = f'inline; filename="error_{filename.replace(".pdf", ".html")}"'
|
response["Content-Disposition"] = (
|
||||||
|
f'inline; filename="error_{filename.replace(".pdf", ".html")}"'
|
||||||
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def export_data_list_pdf(self, data, fields_config, title, filename_prefix, request_user=None):
|
def export_data_list_pdf(
|
||||||
|
self, data, fields_config, title, filename_prefix, request_user=None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Export a list of data as formatted PDF
|
Export a list of data as formatted PDF
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data: QuerySet or list of model instances
|
data: QuerySet or list of model instances
|
||||||
fields_config: dict with field names as keys and display names as values
|
fields_config: dict with field names as keys and display names as values
|
||||||
@@ -385,34 +407,39 @@ class PDFGenerator:
|
|||||||
request_user: User making the request (for audit purposes)
|
request_user: User making the request (for audit purposes)
|
||||||
"""
|
"""
|
||||||
corporate_settings = self.get_corporate_settings()
|
corporate_settings = self.get_corporate_settings()
|
||||||
logo_base64 = self.get_logo_base64(corporate_settings.get('logo_path', ''))
|
logo_base64 = self.get_logo_base64(corporate_settings.get("logo_path", ""))
|
||||||
|
|
||||||
# Prepare context
|
# Prepare context
|
||||||
context = {
|
context = {
|
||||||
'corporate_settings': corporate_settings,
|
"corporate_settings": corporate_settings,
|
||||||
'logo_base64': logo_base64,
|
"logo_base64": logo_base64,
|
||||||
'title': title,
|
"title": title,
|
||||||
'data': data,
|
"data": data,
|
||||||
'fields_config': fields_config,
|
"fields_config": fields_config,
|
||||||
'generation_date': timezone.now(),
|
"generation_date": timezone.now(),
|
||||||
'generated_by': (request_user.get_full_name()
|
"generated_by": (
|
||||||
if hasattr(request_user, 'get_full_name') and request_user.get_full_name()
|
request_user.get_full_name()
|
||||||
else request_user.username
|
if hasattr(request_user, "get_full_name")
|
||||||
if hasattr(request_user, 'username') and request_user.username
|
and request_user.get_full_name()
|
||||||
else 'System'),
|
else (
|
||||||
'total_count': len(data) if hasattr(data, '__len__') else data.count(),
|
request_user.username
|
||||||
|
if hasattr(request_user, "username") and request_user.username
|
||||||
|
else "System"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"total_count": len(data) if hasattr(data, "__len__") else data.count(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Render HTML
|
# Render HTML
|
||||||
html_content = render_to_string('pdf/data_list.html', context)
|
html_content = render_to_string("pdf/data_list.html", context)
|
||||||
|
|
||||||
# Generate CSS
|
# Generate CSS
|
||||||
css_content = self.get_base_css(corporate_settings)
|
css_content = self.get_base_css(corporate_settings)
|
||||||
|
|
||||||
# Generate filename
|
# Generate filename
|
||||||
timestamp = timezone.now().strftime('%Y%m%d_%H%M%S')
|
timestamp = timezone.now().strftime("%Y%m%d_%H%M%S")
|
||||||
filename = f"{filename_prefix}_{timestamp}.pdf"
|
filename = f"{filename_prefix}_{timestamp}.pdf"
|
||||||
|
|
||||||
return self.generate_pdf_response(html_content, filename, css_content)
|
return self.generate_pdf_response(html_content, filename, css_content)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user