- Add VierteljahresNachweis model for quarterly document tracking - Remove studiennachweis_erforderlich field (now always required) - Fix modal edit view to include studiennachweis section - Implement automatic DestinataerUnterstuetzung creation when requirements met - Set payment due dates to exact quarter end dates (Mar 31, Jun 30, Sep 30, Dec 31) - Add quarterly confirmation CRUD views with modal and full-screen editing - Update templates with comprehensive quarterly management interface - Include proper validation, status tracking, and progress indicators
1164 lines
33 KiB
Python
1164 lines
33 KiB
Python
from django.contrib import admin
|
|
from django.db.models import Count, Sum
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from django.utils.html import format_html
|
|
from django.utils.safestring import mark_safe
|
|
|
|
from . import models
|
|
from .models import (AppConfiguration, AuditLog, BackupJob, BankTransaction,
|
|
CSVImport, Destinataer, DestinataerUnterstuetzung,
|
|
DokumentLink, Foerderung, Land, LandVerpachtung, Paechter, Person,
|
|
Rentmeister, StiftungsKonto, UnterstuetzungWiederkehrend,
|
|
Verwaltungskosten, VierteljahresNachweis)
|
|
|
|
|
|
@admin.register(CSVImport)
|
|
class CSVImportAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"import_type",
|
|
"filename",
|
|
"status",
|
|
"total_rows",
|
|
"imported_rows",
|
|
"failed_rows",
|
|
"created_by",
|
|
"started_at",
|
|
"duration_display",
|
|
]
|
|
list_filter = ["import_type", "status", "started_at"]
|
|
search_fields = ["filename", "created_by"]
|
|
readonly_fields = ["id", "started_at", "completed_at", "get_success_rate"]
|
|
ordering = ["-started_at"]
|
|
|
|
fieldsets = (
|
|
(
|
|
"Grundinformationen",
|
|
{"fields": ("import_type", "filename", "file_size", "status")},
|
|
),
|
|
(
|
|
"Ergebnisse",
|
|
{
|
|
"fields": (
|
|
"total_rows",
|
|
"imported_rows",
|
|
"failed_rows",
|
|
"get_success_rate",
|
|
"error_log",
|
|
)
|
|
},
|
|
),
|
|
("Metadaten", {"fields": ("created_by", "started_at", "completed_at")}),
|
|
)
|
|
|
|
def duration_display(self, obj):
|
|
duration = obj.get_duration()
|
|
if duration:
|
|
return f"{duration.total_seconds():.1f}s"
|
|
return "-"
|
|
|
|
duration_display.short_description = "Dauer"
|
|
|
|
def get_success_rate(self, obj):
|
|
rate = obj.get_success_rate()
|
|
if rate >= 90:
|
|
color = "success"
|
|
elif rate >= 70:
|
|
color = "warning"
|
|
else:
|
|
color = "danger"
|
|
return format_html('<span class="badge bg-{}">{:.1f}%</span>', color, rate)
|
|
|
|
get_success_rate.short_description = "Erfolgsrate"
|
|
|
|
|
|
@admin.register(Person)
|
|
class PersonAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"nachname",
|
|
"vorname",
|
|
"familienzweig",
|
|
"geburtsdatum",
|
|
"email",
|
|
"iban_display",
|
|
]
|
|
list_filter = ["familienzweig", "geburtsdatum"]
|
|
search_fields = ["nachname", "vorname", "email", "familienzweig"]
|
|
ordering = ["nachname", "vorname"]
|
|
readonly_fields = ["id"]
|
|
|
|
fieldsets = (
|
|
(
|
|
"Persönliche Daten",
|
|
{"fields": ("vorname", "nachname", "geburtsdatum", "email", "telefon")},
|
|
),
|
|
("Stiftungsdaten", {"fields": ("familienzweig", "iban", "adresse")}),
|
|
("Zusätzlich", {"fields": ("notizen", "aktiv")}),
|
|
("System", {"fields": ("id",), "classes": ("collapse",)}),
|
|
)
|
|
|
|
def iban_display(self, obj):
|
|
if obj.iban:
|
|
return format_html(
|
|
'<span style="font-family: monospace;">{}</span>', obj.iban
|
|
)
|
|
return "-"
|
|
|
|
iban_display.short_description = "IBAN"
|
|
|
|
def get_queryset(self, request):
|
|
return (
|
|
super()
|
|
.get_queryset(request)
|
|
.annotate(total_foerderungen=Sum("foerderung__betrag"))
|
|
)
|
|
|
|
|
|
@admin.register(Paechter)
|
|
class PaechterAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"nachname",
|
|
"vorname",
|
|
"pachtnummer",
|
|
"pachtzins_aktuell",
|
|
"landwirtschaftliche_ausbildung",
|
|
"aktiv",
|
|
]
|
|
list_filter = ["landwirtschaftliche_ausbildung", "aktiv"]
|
|
search_fields = ["nachname", "vorname", "email", "pachtnummer"]
|
|
ordering = ["nachname", "vorname"]
|
|
readonly_fields = ["id"]
|
|
|
|
fieldsets = (
|
|
(
|
|
"Persönliche Daten",
|
|
{"fields": ("vorname", "nachname", "geburtsdatum", "email", "telefon")},
|
|
),
|
|
(
|
|
"Pacht-Informationen",
|
|
{
|
|
"fields": (
|
|
"pachtnummer",
|
|
"pachtbeginn_erste",
|
|
"pachtende_letzte",
|
|
"pachtzins_aktuell",
|
|
)
|
|
},
|
|
),
|
|
(
|
|
"Landwirtschaftliche Qualifikation",
|
|
{
|
|
"fields": (
|
|
"landwirtschaftliche_ausbildung",
|
|
"berufserfahrung_jahre",
|
|
"spezialisierung",
|
|
)
|
|
},
|
|
),
|
|
("Kontaktdaten", {"fields": ("iban", "strasse", "plz", "ort")}),
|
|
("Pächter-Typ", {"fields": ("personentyp",)}),
|
|
("Zusätzlich", {"fields": ("notizen", "aktiv")}),
|
|
("System", {"fields": ("id",), "classes": ("collapse",)}),
|
|
)
|
|
|
|
def iban_display(self, obj):
|
|
if obj.iban:
|
|
return format_html(
|
|
'<span style="font-family: monospace;">{}</span>', obj.iban
|
|
)
|
|
return "-"
|
|
|
|
iban_display.short_description = "IBAN"
|
|
|
|
|
|
@admin.register(Destinataer)
|
|
class DestinataerAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"nachname",
|
|
"vorname",
|
|
"familienzweig",
|
|
"berufsgruppe",
|
|
"institution",
|
|
"finanzielle_notlage",
|
|
"aktiv",
|
|
]
|
|
list_filter = ["familienzweig", "berufsgruppe", "finanzielle_notlage", "aktiv"]
|
|
search_fields = ["nachname", "vorname", "email", "institution", "familienzweig"]
|
|
ordering = ["nachname", "vorname"]
|
|
readonly_fields = ["id"]
|
|
|
|
fieldsets = (
|
|
(
|
|
"Persönliche Daten",
|
|
{"fields": ("vorname", "nachname", "geburtsdatum", "email", "telefon")},
|
|
),
|
|
(
|
|
"Berufliche Informationen",
|
|
{"fields": ("berufsgruppe", "ausbildungsstand", "institution")},
|
|
),
|
|
(
|
|
"Projekt & Finanzen",
|
|
{
|
|
"fields": (
|
|
"projekt_beschreibung",
|
|
"jaehrliches_einkommen",
|
|
"finanzielle_notlage",
|
|
)
|
|
},
|
|
),
|
|
(
|
|
"Stiftungsdaten",
|
|
{"fields": ("familienzweig", "iban", "strasse", "plz", "ort")},
|
|
),
|
|
("Zusätzlich", {"fields": ("notizen", "aktiv")}),
|
|
("System", {"fields": ("id",), "classes": ("collapse",)}),
|
|
)
|
|
|
|
def iban_display(self, obj):
|
|
if obj.iban:
|
|
return format_html(
|
|
'<span style="font-family: monospace;">{}</span>', obj.iban
|
|
)
|
|
return "-"
|
|
|
|
iban_display.short_description = "IBAN"
|
|
|
|
|
|
@admin.register(Land)
|
|
class LandAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"lfd_nr",
|
|
"gemeinde",
|
|
"gemarkung",
|
|
"flur",
|
|
"flurstueck",
|
|
"groesse_qm",
|
|
"verp_flaeche_aktuell",
|
|
"verpachtungsgrad_display",
|
|
"aktiv",
|
|
]
|
|
list_filter = ["gemeinde", "gemarkung", "aktiv"]
|
|
search_fields = ["lfd_nr", "gemeinde", "gemarkung", "flur", "flurstueck"]
|
|
ordering = ["gemeinde", "gemarkung", "flur", "flurstueck"]
|
|
readonly_fields = ["id", "gesamtflaeche_berechnet", "verpachtungsgrad_berechnet"]
|
|
|
|
fieldsets = (
|
|
("Identifikation", {"fields": ("lfd_nr", "ew_nummer")}),
|
|
("Gerichtliche Zuständigkeit", {"fields": ("amtsgericht",)}),
|
|
(
|
|
"Verwaltungsstruktur",
|
|
{"fields": ("gemeinde", "gemarkung", "flur", "flurstueck")},
|
|
),
|
|
(
|
|
"Flächenangaben",
|
|
{
|
|
"fields": (
|
|
"groesse_qm",
|
|
"gruenland_qm",
|
|
"acker_qm",
|
|
"wald_qm",
|
|
"sonstiges_qm",
|
|
)
|
|
},
|
|
),
|
|
(
|
|
"Verpachtung",
|
|
{
|
|
"fields": (
|
|
"verpachtete_gesamtflaeche",
|
|
"flaeche_alte_liste",
|
|
"verp_flaeche_aktuell",
|
|
)
|
|
},
|
|
),
|
|
("Steuern und Abgaben", {"fields": ("anteil_grundsteuer", "anteil_lwk")}),
|
|
("Status", {"fields": ("aktiv", "notizen")}),
|
|
(
|
|
"System",
|
|
{
|
|
"fields": ("id", "erstellt_am", "aktualisiert_am"),
|
|
"classes": ("collapse",),
|
|
},
|
|
),
|
|
)
|
|
|
|
def verpachtungsgrad_display(self, obj):
|
|
grad = obj.get_verpachtungsgrad()
|
|
if grad > 90:
|
|
color = "green"
|
|
elif grad > 70:
|
|
color = "orange"
|
|
else:
|
|
color = "red"
|
|
return format_html('<span style="color: {};">{:.1f}%</span>', color, grad)
|
|
|
|
verpachtungsgrad_display.short_description = "Verpachtungsgrad"
|
|
|
|
def gesamtflaeche_berechnet(self, obj):
|
|
return f"{obj.get_gesamtflaeche():.2f} qm"
|
|
|
|
gesamtflaeche_berechnet.short_description = "Berechnete Gesamtfläche"
|
|
|
|
def verpachtungsgrad_berechnet(self, obj):
|
|
return f"{obj.get_verpachtungsgrad():.1f}%"
|
|
|
|
verpachtungsgrad_berechnet.short_description = "Verpachtungsgrad"
|
|
|
|
|
|
@admin.register(LandVerpachtung)
|
|
class LandVerpachtungAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"land",
|
|
"paechter",
|
|
"pachtzins_pauschal",
|
|
"pachtbeginn",
|
|
"pachtende",
|
|
"status_display",
|
|
"erstellt_am",
|
|
]
|
|
list_filter = ["status", "pachtbeginn", "pachtende", "erstellt_am"]
|
|
search_fields = ["land__lfd_nr", "land__gemeinde", "paechter__vorname", "paechter__nachname", "vertragsnummer"]
|
|
ordering = ["-erstellt_am"]
|
|
readonly_fields = ["id", "erstellt_am", "aktualisiert_am"]
|
|
|
|
fieldsets = (
|
|
("Verpachtungsdetails", {
|
|
"fields": ("land", "paechter", "vertragsnummer", "status")
|
|
}),
|
|
("Laufzeit", {
|
|
"fields": ("pachtbeginn", "pachtende", "verlaengerung_klausel")
|
|
}),
|
|
("Fläche", {
|
|
"fields": ("verpachtete_flaeche",)
|
|
}),
|
|
("Pachtzins", {
|
|
"fields": ("pachtzins_pauschal", "pachtzins_pro_ha", "zahlungsweise")
|
|
}),
|
|
("Umsatzsteuer", {
|
|
"fields": ("ust_option", "ust_satz"),
|
|
"classes": ("collapse",)
|
|
}),
|
|
("Umlagen", {
|
|
"fields": ("grundsteuer_umlage", "versicherungen_umlage", "verbandsbeitraege_umlage", "jagdpacht_anteil_umlage"),
|
|
"classes": ("collapse",)
|
|
}),
|
|
("Zusatzinformationen", {
|
|
"fields": ("bemerkungen",),
|
|
"classes": ("collapse",)
|
|
}),
|
|
("System", {
|
|
"fields": ("id", "erstellt_am", "aktualisiert_am"),
|
|
"classes": ("collapse",)
|
|
}),
|
|
)
|
|
|
|
def status_display(self, obj):
|
|
colors = {
|
|
'aktiv': 'green',
|
|
'beendet': 'red',
|
|
'geplant': 'orange',
|
|
'gekündigt': 'red'
|
|
}
|
|
color = colors.get(obj.status, 'black')
|
|
return format_html(
|
|
'<span style="color: {}; font-weight: bold;">{}</span>',
|
|
color,
|
|
obj.get_status_display()
|
|
)
|
|
|
|
status_display.short_description = "Status"
|
|
|
|
|
|
@admin.register(DokumentLink)
|
|
class DokumentLinkAdmin(admin.ModelAdmin):
|
|
list_display = ["titel", "kontext", "paperless_document_id"]
|
|
list_filter = ["kontext"]
|
|
search_fields = ["titel", "kontext"]
|
|
ordering = ["titel"]
|
|
readonly_fields = ["id"]
|
|
|
|
fieldsets = (
|
|
(
|
|
"Dokument",
|
|
{"fields": ("titel", "kontext", "paperless_document_id", "beschreibung")},
|
|
),
|
|
("System", {"fields": ("id",), "classes": ("collapse",)}),
|
|
)
|
|
|
|
|
|
@admin.register(Foerderung)
|
|
class FoerderungAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"destinataer",
|
|
"jahr",
|
|
"betrag",
|
|
"verwendungsnachweis_link",
|
|
"total_for_destinataer",
|
|
]
|
|
list_filter = ["jahr", "destinataer__familienzweig"]
|
|
search_fields = [
|
|
"destinataer__nachname",
|
|
"destinataer__vorname",
|
|
"destinataer__familienzweig",
|
|
]
|
|
ordering = ["-jahr", "-betrag"]
|
|
readonly_fields = ["id"]
|
|
|
|
fieldsets = (
|
|
(
|
|
"Förderung",
|
|
{
|
|
"fields": (
|
|
"destinataer",
|
|
"person",
|
|
"jahr",
|
|
"betrag",
|
|
"kategorie",
|
|
"status",
|
|
)
|
|
},
|
|
),
|
|
("Dokumentation", {"fields": ("verwendungsnachweis", "bemerkungen")}),
|
|
("Daten", {"fields": ("antragsdatum", "entscheidungsdatum")}),
|
|
("System", {"fields": ("id",), "classes": ("collapse",)}),
|
|
)
|
|
|
|
def verwendungsnachweis_link(self, obj):
|
|
if obj.verwendungsnachweis:
|
|
return format_html(
|
|
'<a href="{}">{}</a>',
|
|
reverse(
|
|
"admin:stiftung_dokumentlink_change",
|
|
args=[obj.verwendungsnachweis.id],
|
|
),
|
|
obj.verwendungsnachweis.titel,
|
|
)
|
|
return "-"
|
|
|
|
verwendungsnachweis_link.short_description = "Verwendungsnachweis"
|
|
|
|
def total_for_destinataer(self, obj):
|
|
total = (
|
|
Foerderung.objects.filter(destinataer=obj.destinataer).aggregate(
|
|
Sum("betrag")
|
|
)["betrag__sum"]
|
|
or 0
|
|
)
|
|
return f"€{total:,.2f}"
|
|
|
|
total_for_destinataer.short_description = "Gesamt für Destinatär"
|
|
|
|
|
|
@admin.register(Rentmeister)
|
|
class RentmeisterAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"__str__",
|
|
"email",
|
|
"telefon",
|
|
"seit_datum",
|
|
"bis_datum",
|
|
"aktiv",
|
|
"monatliche_verguetung",
|
|
]
|
|
list_filter = ["aktiv", "seit_datum", "anrede"]
|
|
search_fields = ["vorname", "nachname", "email", "telefon", "ort"]
|
|
ordering = ["nachname", "vorname"]
|
|
readonly_fields = ["id", "erstellt_am", "aktualisiert_am"]
|
|
|
|
fieldsets = (
|
|
("Persönliche Daten", {"fields": ("anrede", "vorname", "nachname", "titel")}),
|
|
(
|
|
"Kontaktdaten",
|
|
{"fields": ("email", "telefon", "mobil", "strasse", "plz", "ort")},
|
|
),
|
|
(
|
|
"Bankdaten",
|
|
{"fields": ("iban", "bic", "bank_name"), "classes": ["collapse"]},
|
|
),
|
|
(
|
|
"Stiftungsdaten",
|
|
{
|
|
"fields": (
|
|
"seit_datum",
|
|
"bis_datum",
|
|
"aktiv",
|
|
"monatliche_verguetung",
|
|
"km_pauschale",
|
|
)
|
|
},
|
|
),
|
|
(
|
|
"Zusätzliche Informationen",
|
|
{"fields": ("notizen",), "classes": ["collapse"]},
|
|
),
|
|
(
|
|
"System",
|
|
{
|
|
"fields": ("id", "erstellt_am", "aktualisiert_am"),
|
|
"classes": ["collapse"],
|
|
},
|
|
),
|
|
)
|
|
|
|
|
|
@admin.register(StiftungsKonto)
|
|
class StiftungsKontoAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"kontoname",
|
|
"bank_name",
|
|
"konto_typ",
|
|
"saldo",
|
|
"saldo_datum",
|
|
"aktiv",
|
|
]
|
|
list_filter = ["konto_typ", "aktiv", "bank_name"]
|
|
search_fields = ["kontoname", "bank_name", "iban"]
|
|
ordering = ["bank_name", "kontoname"]
|
|
readonly_fields = ["id", "erstellt_am", "aktualisiert_am"]
|
|
|
|
fieldsets = (
|
|
(
|
|
"Kontodaten",
|
|
{"fields": ("kontoname", "bank_name", "iban", "bic", "konto_typ")},
|
|
),
|
|
(
|
|
"Finanzdaten",
|
|
{"fields": ("saldo", "saldo_datum", "zinssatz", "laufzeit_bis")},
|
|
),
|
|
("Status", {"fields": ("aktiv", "notizen")}),
|
|
(
|
|
"System",
|
|
{
|
|
"fields": ("id", "erstellt_am", "aktualisiert_am"),
|
|
"classes": ["collapse"],
|
|
},
|
|
),
|
|
)
|
|
|
|
|
|
@admin.register(Verwaltungskosten)
|
|
class VerwaltungskostenAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"bezeichnung",
|
|
"kategorie",
|
|
"betrag",
|
|
"datum",
|
|
"status",
|
|
"rentmeister",
|
|
"konto",
|
|
]
|
|
list_filter = ["kategorie", "status", "datum", "rentmeister", "konto"]
|
|
search_fields = [
|
|
"bezeichnung",
|
|
"lieferant_firma",
|
|
"rechnungsnummer",
|
|
"beschreibung",
|
|
]
|
|
ordering = ["-datum", "-erstellt_am"]
|
|
readonly_fields = ["id", "erstellt_am", "aktualisiert_am"]
|
|
date_hierarchy = "datum"
|
|
|
|
fieldsets = (
|
|
(
|
|
"Grunddaten",
|
|
{"fields": ("bezeichnung", "kategorie", "betrag", "datum", "status")},
|
|
),
|
|
("Zuordnung", {"fields": ("rentmeister", "konto")}),
|
|
(
|
|
"Lieferant/Rechnung",
|
|
{"fields": ("lieferant_firma", "rechnungsnummer"), "classes": ["collapse"]},
|
|
),
|
|
(
|
|
"Fahrtkosten",
|
|
{
|
|
"fields": ("km_anzahl", "km_satz", "von_ort", "nach_ort", "zweck"),
|
|
"classes": ["collapse"],
|
|
"description": 'Nur für Kategorie "Fahrtkosten" relevant',
|
|
},
|
|
),
|
|
(
|
|
"Zusätzliche Informationen",
|
|
{"fields": ("beschreibung", "notizen"), "classes": ["collapse"]},
|
|
),
|
|
(
|
|
"System",
|
|
{
|
|
"fields": ("id", "erstellt_am", "aktualisiert_am"),
|
|
"classes": ["collapse"],
|
|
},
|
|
),
|
|
)
|
|
|
|
|
|
@admin.register(BankTransaction)
|
|
class BankTransactionAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"datum",
|
|
"konto",
|
|
"betrag",
|
|
"empfaenger_zahlungspflichtiger",
|
|
"transaction_type",
|
|
"status",
|
|
"verwaltungskosten",
|
|
]
|
|
list_filter = ["konto", "transaction_type", "status", "datum", "importiert_am"]
|
|
search_fields = ["verwendungszweck", "empfaenger_zahlungspflichtiger", "referenz"]
|
|
readonly_fields = ["importiert_am", "import_datei"]
|
|
ordering = ["-datum", "-importiert_am"]
|
|
|
|
fieldsets = (
|
|
("Basisdaten", {"fields": ("konto", "datum", "valuta", "betrag", "waehrung")}),
|
|
(
|
|
"Transaktionsdetails",
|
|
{
|
|
"fields": (
|
|
"verwendungszweck",
|
|
"empfaenger_zahlungspflichtiger",
|
|
"iban_gegenpartei",
|
|
"bic_gegenpartei",
|
|
"referenz",
|
|
"transaction_type",
|
|
)
|
|
},
|
|
),
|
|
("Verwaltung", {"fields": ("status", "kommentare", "verwaltungskosten")}),
|
|
(
|
|
"Import-Information",
|
|
{
|
|
"fields": ("import_datei", "importiert_am", "saldo_nach_buchung"),
|
|
"classes": ("collapse",),
|
|
},
|
|
),
|
|
)
|
|
|
|
def get_queryset(self, request):
|
|
return (
|
|
super().get_queryset(request).select_related("konto", "verwaltungskosten")
|
|
)
|
|
|
|
|
|
@admin.register(AuditLog)
|
|
class AuditLogAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"timestamp",
|
|
"username",
|
|
"action",
|
|
"entity_type",
|
|
"entity_name",
|
|
"ip_address",
|
|
]
|
|
list_filter = ["action", "entity_type", "timestamp", "username"]
|
|
search_fields = ["username", "entity_name", "description", "ip_address"]
|
|
readonly_fields = [
|
|
"id",
|
|
"timestamp",
|
|
"user",
|
|
"username",
|
|
"action",
|
|
"entity_type",
|
|
"entity_id",
|
|
"entity_name",
|
|
"description",
|
|
"changes",
|
|
"ip_address",
|
|
"user_agent",
|
|
"session_key",
|
|
]
|
|
ordering = ["-timestamp"]
|
|
date_hierarchy = "timestamp"
|
|
|
|
fieldsets = (
|
|
(
|
|
"Benutzer und Zeit",
|
|
{"fields": ("timestamp", "user", "username", "session_key")},
|
|
),
|
|
(
|
|
"Aktion",
|
|
{
|
|
"fields": (
|
|
"action",
|
|
"entity_type",
|
|
"entity_id",
|
|
"entity_name",
|
|
"description",
|
|
)
|
|
},
|
|
),
|
|
("Änderungsdetails", {"fields": ("changes",), "classes": ["collapse"]}),
|
|
(
|
|
"Request-Informationen",
|
|
{"fields": ("ip_address", "user_agent"), "classes": ["collapse"]},
|
|
),
|
|
("System", {"fields": ("id",), "classes": ["collapse"]}),
|
|
)
|
|
|
|
def has_add_permission(self, request):
|
|
return False # Don't allow manual creation
|
|
|
|
def has_change_permission(self, request, obj=None):
|
|
return False # Don't allow editing
|
|
|
|
|
|
@admin.register(BackupJob)
|
|
class BackupJobAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"created_at",
|
|
"backup_type",
|
|
"status",
|
|
"backup_size_display",
|
|
"duration_display",
|
|
"created_by",
|
|
]
|
|
list_filter = ["backup_type", "status", "created_at"]
|
|
search_fields = ["backup_filename", "created_by__username"]
|
|
readonly_fields = [
|
|
"id",
|
|
"created_at",
|
|
"started_at",
|
|
"completed_at",
|
|
"backup_size",
|
|
"get_duration",
|
|
]
|
|
ordering = ["-created_at"]
|
|
|
|
fieldsets = (
|
|
("Job-Details", {"fields": ("backup_type", "status", "created_by")}),
|
|
(
|
|
"Zeitpunkte",
|
|
{"fields": ("created_at", "started_at", "completed_at", "get_duration")},
|
|
),
|
|
(
|
|
"Ergebnis",
|
|
{
|
|
"fields": (
|
|
"backup_filename",
|
|
"backup_size",
|
|
"database_size",
|
|
"files_count",
|
|
)
|
|
},
|
|
),
|
|
("Fehlerbehandlung", {"fields": ("error_message",), "classes": ["collapse"]}),
|
|
("System", {"fields": ("id",), "classes": ["collapse"]}),
|
|
)
|
|
|
|
def backup_size_display(self, obj):
|
|
return obj.get_size_display()
|
|
|
|
backup_size_display.short_description = "Backup-Größe"
|
|
|
|
def duration_display(self, obj):
|
|
duration = obj.get_duration()
|
|
if duration:
|
|
return f"{duration.total_seconds():.1f}s"
|
|
return "-"
|
|
|
|
duration_display.short_description = "Dauer"
|
|
|
|
def has_add_permission(self, request):
|
|
return False # Use the web interface for creating backups
|
|
|
|
|
|
@admin.register(AppConfiguration)
|
|
class AppConfigurationAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"display_name",
|
|
"key",
|
|
"value_display",
|
|
"category",
|
|
"setting_type",
|
|
"is_active",
|
|
"updated_at",
|
|
]
|
|
list_filter = ["category", "setting_type", "is_active"]
|
|
search_fields = ["key", "display_name", "description"]
|
|
readonly_fields = ["id", "created_at", "updated_at"]
|
|
ordering = ["category", "order", "display_name"]
|
|
|
|
fieldsets = (
|
|
(
|
|
"Basic Information",
|
|
{
|
|
"fields": (
|
|
"key",
|
|
"display_name",
|
|
"description",
|
|
"category",
|
|
"setting_type",
|
|
)
|
|
},
|
|
),
|
|
("Value Configuration", {"fields": ("value", "default_value")}),
|
|
("Options", {"fields": ("is_active", "is_system", "order")}),
|
|
(
|
|
"Metadata",
|
|
{"fields": ("created_at", "updated_at"), "classes": ("collapse",)},
|
|
),
|
|
)
|
|
|
|
def value_display(self, obj):
|
|
"""Display value with type formatting"""
|
|
value = obj.value
|
|
if obj.setting_type == "boolean":
|
|
icon = "✅" if obj.get_typed_value() else "❌"
|
|
return format_html("{} {}", icon, value)
|
|
elif obj.setting_type == "url":
|
|
return format_html(
|
|
'<a href="{}" target="_blank">{}</a>',
|
|
value,
|
|
value[:50] + "..." if len(value) > 50 else value,
|
|
)
|
|
elif len(value) > 100:
|
|
return value[:100] + "..."
|
|
return value
|
|
|
|
value_display.short_description = "Current Value"
|
|
|
|
def get_readonly_fields(self, request, obj=None):
|
|
readonly = list(self.readonly_fields)
|
|
if obj and obj.is_system:
|
|
readonly.extend(["key", "setting_type", "is_system"])
|
|
return readonly
|
|
|
|
|
|
@admin.register(DestinataerUnterstuetzung)
|
|
class DestinataerUnterstuetzungAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"__str__",
|
|
"destinataer",
|
|
"betrag",
|
|
"faellig_am",
|
|
"status",
|
|
"wiederkehrend_von",
|
|
"ausgezahlt_am",
|
|
]
|
|
list_filter = ["status", "faellig_am", "erstellt_am", "konto"]
|
|
search_fields = [
|
|
"destinataer__vorname",
|
|
"destinataer__nachname",
|
|
"beschreibung",
|
|
"empfaenger_name",
|
|
]
|
|
readonly_fields = ["id", "erstellt_am", "aktualisiert_am"]
|
|
|
|
fieldsets = (
|
|
(
|
|
"Grundinformationen",
|
|
{
|
|
"fields": (
|
|
"destinataer",
|
|
"konto",
|
|
"betrag",
|
|
"faellig_am",
|
|
"status",
|
|
"beschreibung",
|
|
)
|
|
},
|
|
),
|
|
(
|
|
"Überweisungsdaten",
|
|
{"fields": ("empfaenger_iban", "empfaenger_name", "verwendungszweck")},
|
|
),
|
|
("Zahlungsinformationen", {"fields": ("ausgezahlt_am", "ausgezahlt_von")}),
|
|
("Wiederkehrend", {"fields": ("wiederkehrend_von",)}),
|
|
(
|
|
"Metadaten",
|
|
{
|
|
"fields": ("id", "erstellt_am", "aktualisiert_am"),
|
|
"classes": ("collapse",),
|
|
},
|
|
),
|
|
)
|
|
|
|
|
|
@admin.register(UnterstuetzungWiederkehrend)
|
|
class UnterstuetzungWiederkehrendAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"__str__",
|
|
"destinataer",
|
|
"betrag",
|
|
"intervall",
|
|
"aktiv",
|
|
"naechste_generierung",
|
|
]
|
|
list_filter = ["intervall", "aktiv", "erstellt_am"]
|
|
search_fields = [
|
|
"destinataer__vorname",
|
|
"destinataer__nachname",
|
|
"beschreibung",
|
|
"empfaenger_name",
|
|
]
|
|
readonly_fields = ["id", "erstellt_am"]
|
|
|
|
fieldsets = (
|
|
(
|
|
"Grundinformationen",
|
|
{
|
|
"fields": (
|
|
"destinataer",
|
|
"konto",
|
|
"betrag",
|
|
"intervall",
|
|
"beschreibung",
|
|
"aktiv",
|
|
)
|
|
},
|
|
),
|
|
(
|
|
"Überweisungsdaten",
|
|
{"fields": ("empfaenger_iban", "empfaenger_name", "verwendungszweck")},
|
|
),
|
|
(
|
|
"Zeitplanung",
|
|
{
|
|
"fields": (
|
|
"erste_zahlung_am",
|
|
"letzte_zahlung_am",
|
|
"naechste_generierung",
|
|
)
|
|
},
|
|
),
|
|
(
|
|
"Metadaten",
|
|
{"fields": ("id", "erstellt_von", "erstellt_am"), "classes": ("collapse",)},
|
|
),
|
|
)
|
|
|
|
|
|
@admin.register(models.HelpBox)
|
|
class HelpBoxAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"get_page_display",
|
|
"title",
|
|
"is_active",
|
|
"updated_at",
|
|
"updated_by",
|
|
]
|
|
list_filter = ["page_key", "is_active", "updated_at"]
|
|
search_fields = ["title", "content"]
|
|
|
|
fieldsets = (
|
|
("Grundinformationen", {"fields": ("page_key", "title", "is_active")}),
|
|
(
|
|
"Inhalt",
|
|
{
|
|
"fields": ("content",),
|
|
"description": "Markdown wird unterstützt: **fett**, *kursiv*, `code`, [Link](url), Listen mit - oder 1.",
|
|
},
|
|
),
|
|
(
|
|
"Metadaten",
|
|
{
|
|
"fields": ("created_at", "updated_at", "created_by", "updated_by"),
|
|
"classes": ("collapse",),
|
|
},
|
|
),
|
|
)
|
|
|
|
readonly_fields = ["created_at", "updated_at"]
|
|
|
|
def get_page_display(self, obj):
|
|
return obj.get_page_key_display()
|
|
|
|
get_page_display.short_description = "Seite"
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
if not change: # Neues Objekt
|
|
obj.created_by = request.user.username
|
|
obj.updated_by = request.user.username
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
@admin.register(VierteljahresNachweis)
|
|
class VierteljahresNachweisAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"destinataer",
|
|
"jahr",
|
|
"quartal",
|
|
"status",
|
|
"completion_percentage",
|
|
"faelligkeitsdatum",
|
|
"is_overdue_display",
|
|
"eingereicht_am",
|
|
"geprueft_von",
|
|
]
|
|
list_filter = [
|
|
"jahr",
|
|
"quartal",
|
|
"status",
|
|
"studiennachweis_erforderlich",
|
|
"studiennachweis_eingereicht",
|
|
"einkommenssituation_bestaetigt",
|
|
"vermogenssituation_bestaetigt",
|
|
"faelligkeitsdatum",
|
|
]
|
|
search_fields = [
|
|
"destinataer__vorname",
|
|
"destinataer__nachname",
|
|
"destinataer__email",
|
|
]
|
|
readonly_fields = [
|
|
"id",
|
|
"erstellt_am",
|
|
"aktualisiert_am",
|
|
"completion_percentage",
|
|
"is_overdue_display",
|
|
]
|
|
ordering = ["-jahr", "-quartal", "destinataer__nachname"]
|
|
|
|
fieldsets = (
|
|
(
|
|
"Grundinformationen",
|
|
{
|
|
"fields": (
|
|
"destinataer",
|
|
"jahr",
|
|
"quartal",
|
|
"status",
|
|
"faelligkeitsdatum",
|
|
)
|
|
},
|
|
),
|
|
(
|
|
"Studiennachweis",
|
|
{
|
|
"fields": (
|
|
"studiennachweis_erforderlich",
|
|
"studiennachweis_eingereicht",
|
|
"studiennachweis_datei",
|
|
"studiennachweis_bemerkung",
|
|
),
|
|
"classes": ("collapse",),
|
|
},
|
|
),
|
|
(
|
|
"Einkommenssituation",
|
|
{
|
|
"fields": (
|
|
"einkommenssituation_bestaetigt",
|
|
"einkommenssituation_text",
|
|
"einkommenssituation_datei",
|
|
),
|
|
"classes": ("collapse",),
|
|
},
|
|
),
|
|
(
|
|
"Vermögenssituation",
|
|
{
|
|
"fields": (
|
|
"vermogenssituation_bestaetigt",
|
|
"vermogenssituation_text",
|
|
"vermogenssituation_datei",
|
|
),
|
|
"classes": ("collapse",),
|
|
},
|
|
),
|
|
(
|
|
"Weitere Dokumente",
|
|
{
|
|
"fields": (
|
|
"weitere_dokumente",
|
|
"weitere_dokumente_beschreibung",
|
|
),
|
|
"classes": ("collapse",),
|
|
},
|
|
),
|
|
(
|
|
"Verwaltung & Prüfung",
|
|
{
|
|
"fields": (
|
|
"interne_notizen",
|
|
"eingereicht_am",
|
|
"geprueft_am",
|
|
"geprueft_von",
|
|
),
|
|
"classes": ("collapse",),
|
|
},
|
|
),
|
|
(
|
|
"Metadaten",
|
|
{
|
|
"fields": (
|
|
"id",
|
|
"erstellt_am",
|
|
"aktualisiert_am",
|
|
"completion_percentage",
|
|
"is_overdue_display",
|
|
)
|
|
},
|
|
),
|
|
)
|
|
|
|
def completion_percentage(self, obj):
|
|
"""Show completion percentage as colored badge"""
|
|
percentage = obj.get_completion_percentage()
|
|
if percentage == 100:
|
|
color = "success"
|
|
elif percentage >= 70:
|
|
color = "info"
|
|
elif percentage >= 30:
|
|
color = "warning"
|
|
else:
|
|
color = "danger"
|
|
|
|
return format_html(
|
|
'<span class="badge bg-{}">{} %</span>',
|
|
color,
|
|
percentage
|
|
)
|
|
completion_percentage.short_description = "Fortschritt"
|
|
|
|
def is_overdue_display(self, obj):
|
|
"""Display overdue status with icon"""
|
|
if obj.is_overdue():
|
|
return format_html(
|
|
'<span class="text-danger"><i class="fas fa-exclamation-triangle"></i> Ja</span>'
|
|
)
|
|
return format_html(
|
|
'<span class="text-success"><i class="fas fa-check"></i> Nein</span>'
|
|
)
|
|
is_overdue_display.short_description = "Überfällig"
|
|
|
|
actions = ["mark_as_approved", "mark_as_needs_revision"]
|
|
|
|
def mark_as_approved(self, request, queryset):
|
|
"""Bulk action to approve submitted confirmations"""
|
|
count = 0
|
|
for nachweis in queryset.filter(status="eingereicht"):
|
|
nachweis.status = "geprueft"
|
|
nachweis.geprueft_am = timezone.now()
|
|
nachweis.geprueft_von = request.user
|
|
nachweis.save()
|
|
count += 1
|
|
|
|
if count:
|
|
self.message_user(
|
|
request,
|
|
f"{count} Nachweise wurden als geprüft und freigegeben markiert."
|
|
)
|
|
else:
|
|
self.message_user(
|
|
request,
|
|
"Keine eingereichten Nachweise gefunden.",
|
|
level="warning"
|
|
)
|
|
mark_as_approved.short_description = "Ausgewählte Nachweise freigeben"
|
|
|
|
def mark_as_needs_revision(self, request, queryset):
|
|
"""Bulk action to mark confirmations as needing revision"""
|
|
count = queryset.exclude(status__in=["offen", "nachbesserung"]).update(
|
|
status="nachbesserung"
|
|
)
|
|
if count:
|
|
self.message_user(
|
|
request,
|
|
f"{count} Nachweise wurden als nachbesserungsbedürftig markiert."
|
|
)
|
|
mark_as_needs_revision.short_description = "Nachbesserung erforderlich markieren"
|
|
|
|
|
|
# Customize admin site
|
|
admin.site.site_header = "Stiftungsverwaltung Administration"
|
|
admin.site.site_title = "Stiftungsverwaltung Admin"
|
|
admin.site.index_title = "Willkommen zur Stiftungsverwaltung"
|