Fix payment system balance integration and add calendar functionality

- Implement automated payment tracking with Django signals
- Fix duplicate transaction creation with unique referenz system
- Add calendar system with CRUD operations and event management
- Reorganize navigation menu (rename sections, move admin functions)
- Replace Geschichte editor with EasyMDE markdown editor
- Add management commands for balance reconciliation
- Create missing transactions for previously paid payments
- Ensure account balances accurately reflect all payment activity

Features added:
- Calendar entries creation and administration via menu
- Payment status tracking with automatic balance updates
- Duplicate prevention for payment transactions
- Markdown editor with live preview for Geschichte pages
- Database reconciliation tools for payment/balance sync

Bug fixes:
- Resolved IntegrityError on payment status changes
- Fixed missing account balance updates for paid payments
- Prevented duplicate balance deductions on re-saves
- Corrected menu structure and admin function placement
This commit is contained in:
2025-10-05 00:38:18 +02:00
parent 2961f376c3
commit c289cc3c58
36 changed files with 4039 additions and 99 deletions

View File

@@ -14,3 +14,9 @@ class StiftungConfig(AppConfig):
except ImportError: except ImportError:
# django-otp not installed # django-otp not installed
pass pass
# Import signals to register them
try:
import stiftung.signals
except ImportError:
pass

View File

@@ -1648,14 +1648,29 @@ class GeschichteSeiteForm(forms.ModelForm):
def clean_slug(self): def clean_slug(self):
slug = self.cleaned_data.get('slug') slug = self.cleaned_data.get('slug')
titel = self.cleaned_data.get('titel') titel = self.cleaned_data.get('titel', '')
if not slug and titel: if not slug and titel:
# Auto-generate slug from title # Auto-generate slug from title
from django.utils.text import slugify from django.utils.text import slugify
slug = slugify(titel) slug = slugify(titel)
if not slug:
raise forms.ValidationError('Slug ist erforderlich. Bitte geben Sie einen Titel ein.')
return slug return slug
def clean(self):
cleaned_data = super().clean()
titel = cleaned_data.get('titel', '')
slug = cleaned_data.get('slug', '')
# Auto-generate slug if empty
if titel and not slug:
from django.utils.text import slugify
cleaned_data['slug'] = slugify(titel)
return cleaned_data
class GeschichteBildForm(forms.ModelForm): class GeschichteBildForm(forms.ModelForm):

View File

@@ -0,0 +1,58 @@
from django.core.management.base import BaseCommand
from stiftung.models import StiftungsKalenderEintrag
from datetime import date, timedelta
class Command(BaseCommand):
help = 'Creates sample calendar events for testing'
def handle(self, *args, **options):
today = date.today()
events = [
{
'titel': 'Vorstandssitzung',
'beschreibung': 'Monatliche Vorstandssitzung zur Besprechung aktueller Stiftungsangelegenheiten',
'datum': today + timedelta(days=7),
'kategorie': 'termin',
'prioritaet': 'hoch',
},
{
'titel': 'Zahlungserinnerung Familie Müller',
'beschreibung': 'Quartalsweise Unterstützung €500',
'datum': today + timedelta(days=3),
'kategorie': 'zahlung',
'prioritaet': 'kritisch',
},
{
'titel': 'Pachtvertrag Müller läuft aus',
'beschreibung': 'Vertrag für Grundstück A123 muss verlängert werden',
'datum': today + timedelta(days=30),
'kategorie': 'vertrag',
'prioritaet': 'hoch',
},
{
'titel': 'Geburtstag Maria Schmidt',
'beschreibung': '75. Geburtstag',
'datum': today + timedelta(days=5),
'kategorie': 'geburtstag',
'prioritaet': 'normal',
}
]
created_count = 0
for event_data in events:
event, created = StiftungsKalenderEintrag.objects.get_or_create(
titel=event_data['titel'],
defaults=event_data
)
if created:
created_count += 1
self.stdout.write(f'Created: {event.titel}')
else:
self.stdout.write(f'Already exists: {event.titel}')
total = StiftungsKalenderEintrag.objects.count()
self.stdout.write(
self.style.SUCCESS(f'✅ Created {created_count} new events. Total: {total}')
)

View File

@@ -0,0 +1,169 @@
"""
Management command to fix account balances for existing paid payments.
This command will:
1. Find all payments marked as 'ausgezahlt' (paid)
2. Check if corresponding bank transactions exist
3. Create missing bank transactions
4. Update account balances to reflect all paid payments
Usage:
python manage.py fix_account_balances
"""
from django.core.management.base import BaseCommand
from django.utils import timezone
from decimal import Decimal
from stiftung.models import DestinataerUnterstuetzung, BankTransaction, StiftungsKonto
class Command(BaseCommand):
help = 'Fix account balances for existing paid payments'
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be done without making changes',
)
parser.add_argument(
'--account',
type=str,
help='Only process payments for specific account (by kontoname)',
)
def handle(self, *args, **options):
dry_run = options['dry_run']
account_filter = options.get('account')
self.stdout.write(
self.style.SUCCESS('🔍 Analyzing paid payments and account balances...')
)
if dry_run:
self.stdout.write(
self.style.WARNING('DRY RUN MODE - No changes will be made')
)
# Get all paid payments
paid_payments_query = DestinataerUnterstuetzung.objects.filter(
status='ausgezahlt'
).select_related('konto', 'destinataer')
if account_filter:
paid_payments_query = paid_payments_query.filter(
konto__kontoname__icontains=account_filter
)
paid_payments = paid_payments_query.all()
self.stdout.write(f"Found {paid_payments.count()} paid payments")
# Group payments by account
accounts_data = {}
missing_transactions = []
for payment in paid_payments:
konto_id = payment.konto.id
if konto_id not in accounts_data:
accounts_data[konto_id] = {
'konto': payment.konto,
'payments': [],
'total_paid': Decimal('0.00'),
'missing_transactions': []
}
accounts_data[konto_id]['payments'].append(payment)
accounts_data[konto_id]['total_paid'] += payment.betrag
# Check if bank transaction exists for this payment
existing_transaction = BankTransaction.objects.filter(
konto=payment.konto,
betrag=-payment.betrag, # Negative for outgoing payment
kommentare__contains=f'Unterstützung {payment.id}'
).first()
if not existing_transaction:
accounts_data[konto_id]['missing_transactions'].append(payment)
missing_transactions.append(payment)
# Report findings
self.stdout.write("\n📊 ANALYSIS RESULTS:")
self.stdout.write("=" * 50)
for account_data in accounts_data.values():
konto = account_data['konto']
payments_count = len(account_data['payments'])
total_paid = account_data['total_paid']
missing_count = len(account_data['missing_transactions'])
self.stdout.write(f"\n🏦 {konto.bank_name} - {konto.kontoname}")
self.stdout.write(f" Current Balance: €{konto.saldo}")
self.stdout.write(f" Paid Payments: {payments_count} (Total: €{total_paid})")
self.stdout.write(f" Missing Transactions: {missing_count}")
if missing_count > 0:
expected_balance = konto.saldo - sum(p.betrag for p in account_data['missing_transactions'])
self.stdout.write(
self.style.WARNING(f" Expected Balance after fix: €{expected_balance}")
)
if not missing_transactions:
self.stdout.write(
self.style.SUCCESS("\n✅ All paid payments have corresponding transactions!")
)
return
self.stdout.write(f"\n⚠️ Found {len(missing_transactions)} payments without transactions")
if not dry_run:
self.stdout.write("\n🔧 CREATING MISSING TRANSACTIONS...")
created_count = 0
for payment in missing_transactions:
# Create bank transaction
transaction = BankTransaction.objects.create(
konto=payment.konto,
datum=payment.ausgezahlt_am or payment.faellig_am,
valuta=payment.ausgezahlt_am or payment.faellig_am,
betrag=-payment.betrag, # Negative for outgoing payment
waehrung='EUR',
verwendungszweck=f"Unterstützungszahlung: {payment.beschreibung or payment.destinataer.get_full_name()}",
empfaenger_zahlungspflichtiger=payment.empfaenger_name or payment.destinataer.get_full_name(),
iban_gegenpartei=payment.empfaenger_iban or '',
transaction_type='ueberweisung',
status='verified',
kommentare=f'Nachträglich erstellt für Unterstützung {payment.id} - Zahlung vom {payment.ausgezahlt_am or payment.faellig_am}',
)
# Update account balance
payment.konto.saldo -= payment.betrag
payment.konto.saldo_datum = payment.ausgezahlt_am or payment.faellig_am
payment.konto.save()
created_count += 1
self.stdout.write(
f" ✅ Created transaction for {payment.destinataer.get_full_name()}: €{payment.betrag}"
)
self.stdout.write(
self.style.SUCCESS(f"\n🎉 Successfully created {created_count} transactions and updated account balances!")
)
# Show final balances
self.stdout.write("\n📈 UPDATED ACCOUNT BALANCES:")
for account_data in accounts_data.values():
if account_data['missing_transactions']:
konto = account_data['konto']
konto.refresh_from_db() # Get updated balance
self.stdout.write(f" {konto.bank_name} - {konto.kontoname}: €{konto.saldo}")
else:
self.stdout.write("\n📝 DRY RUN - Would create the following transactions:")
for payment in missing_transactions:
self.stdout.write(
f" - {payment.destinataer.get_full_name()}: €{payment.betrag} "
f"on {payment.ausgezahlt_am or payment.faellig_am} "
f"from {payment.konto.kontoname}"
)

View File

@@ -0,0 +1,145 @@
"""
Management command to reconcile account balances with actual transactions.
This command will:
1. Calculate the correct balance based on all bank transactions
2. Update the account balance to match the calculated balance
3. Show discrepancies between stored and calculated balances
Usage:
python manage.py reconcile_balances
"""
from django.core.management.base import BaseCommand
from django.utils import timezone
from decimal import Decimal
from stiftung.models import StiftungsKonto, BankTransaction
class Command(BaseCommand):
help = 'Reconcile account balances with bank transactions'
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be done without making changes',
)
parser.add_argument(
'--account',
type=str,
help='Only process specific account (by kontoname)',
)
def handle(self, *args, **options):
dry_run = options['dry_run']
account_filter = options.get('account')
self.stdout.write(
self.style.SUCCESS('🔍 Reconciling account balances with transactions...')
)
if dry_run:
self.stdout.write(
self.style.WARNING('DRY RUN MODE - No changes will be made')
)
# Get accounts to process
accounts_query = StiftungsKonto.objects.filter(aktiv=True)
if account_filter:
accounts_query = accounts_query.filter(kontoname__icontains=account_filter)
accounts = accounts_query.all()
if not accounts:
self.stdout.write(
self.style.ERROR('No accounts found matching criteria')
)
return
self.stdout.write(f"Processing {accounts.count()} account(s)...")
total_discrepancies = 0
fixed_accounts = 0
for account in accounts:
self.stdout.write(f"\n🏦 {account.bank_name} - {account.kontoname}")
self.stdout.write(f" Stored Balance: €{account.saldo}")
self.stdout.write(f" Last Updated: {account.saldo_datum}")
# Calculate balance from transactions
transactions = BankTransaction.objects.filter(konto=account).order_by('datum')
calculated_balance = Decimal('0.00')
transaction_count = transactions.count()
for transaction in transactions:
calculated_balance += transaction.betrag
self.stdout.write(f" Transactions: {transaction_count}")
self.stdout.write(f" Calculated Balance: €{calculated_balance}")
discrepancy = account.saldo - calculated_balance
if discrepancy != 0:
total_discrepancies += 1
self.stdout.write(
self.style.WARNING(f" ⚠️ Discrepancy: €{discrepancy}")
)
if not dry_run:
# Update the account balance
old_balance = account.saldo
account.saldo = calculated_balance
# Update the balance date to the latest transaction date or today
if transactions.exists():
latest_transaction = transactions.order_by('-datum').first()
account.saldo_datum = latest_transaction.datum
else:
account.saldo_datum = timezone.now().date()
account.save()
fixed_accounts += 1
self.stdout.write(
self.style.SUCCESS(
f" ✅ Updated: €{old_balance} → €{calculated_balance}"
)
)
else:
self.stdout.write(
f" 📝 Would update: €{account.saldo} → €{calculated_balance}"
)
else:
self.stdout.write(
self.style.SUCCESS(" ✅ Balance is correct")
)
# Show recent transactions
if transaction_count > 0:
self.stdout.write(" Recent transactions:")
recent = transactions.order_by('-datum')[:3]
for trans in recent:
self.stdout.write(
f" {trans.datum}: €{trans.betrag} - {trans.verwendungszweck[:50]}"
)
# Summary
self.stdout.write("\n" + "=" * 50)
if dry_run:
self.stdout.write(
f"📊 Found {total_discrepancies} account(s) with balance discrepancies"
)
if total_discrepancies > 0:
self.stdout.write(" Run without --dry-run to fix these discrepancies")
else:
if fixed_accounts > 0:
self.stdout.write(
self.style.SUCCESS(
f"🎉 Fixed {fixed_accounts} account balance(s)"
)
)
else:
self.stdout.write(
self.style.SUCCESS("✅ All account balances were already correct")
)

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2025-10-02 20:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('stiftung', '0037_add_geschichte_models'),
]
operations = [
migrations.AlterField(
model_name='geschichteseite',
name='inhalt',
field=models.TextField(blank=True, verbose_name='Inhalt'),
),
]

View File

@@ -0,0 +1,41 @@
# Generated by Django 5.0.6 on 2025-10-04 20:21
import django.db.models.deletion
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('stiftung', '0038_allow_blank_content'),
]
operations = [
migrations.CreateModel(
name='StiftungsKalenderEintrag',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('titel', models.CharField(max_length=200, verbose_name='Titel')),
('beschreibung', models.TextField(blank=True, verbose_name='Beschreibung')),
('datum', models.DateField(verbose_name='Datum')),
('uhrzeit', models.TimeField(blank=True, null=True, verbose_name='Uhrzeit')),
('ganztags', models.BooleanField(default=True, verbose_name='Ganztägig')),
('kategorie', models.CharField(choices=[('termin', 'Termin/Meeting'), ('zahlung', 'Zahlungserinnerung'), ('deadline', 'Frist/Deadline'), ('geburtstag', 'Geburtstag'), ('vertrag', 'Vertrag läuft aus'), ('pruefung', 'Prüfung/Nachweis'), ('sonstiges', 'Sonstiges')], default='termin', max_length=20, verbose_name='Kategorie')),
('prioritaet', models.CharField(choices=[('niedrig', 'Niedrig'), ('normal', 'Normal'), ('hoch', 'Hoch'), ('kritisch', 'Kritisch')], default='normal', max_length=20, verbose_name='Priorität')),
('erledigt', models.BooleanField(default=False, verbose_name='Erledigt')),
('erledigt_am', models.DateTimeField(blank=True, null=True, verbose_name='Erledigt am')),
('erstellt_von', models.CharField(blank=True, max_length=100, null=True, verbose_name='Erstellt von')),
('erstellt_am', models.DateTimeField(auto_now_add=True, verbose_name='Erstellt am')),
('aktualisiert_am', models.DateTimeField(auto_now=True, verbose_name='Aktualisiert am')),
('destinataer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='stiftung.destinataer', verbose_name='Bezogener Destinatär')),
('verpachtung', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='stiftung.landverpachtung', verbose_name='Bezogene Verpachtung')),
],
options={
'verbose_name': 'Kalender Eintrag',
'verbose_name_plural': 'Kalender Einträge',
'ordering': ['datum', 'uhrzeit'],
'indexes': [models.Index(fields=['datum'], name='stiftung_st_datum_9e97ed_idx'), models.Index(fields=['kategorie', 'datum'], name='stiftung_st_kategor_d7c7f9_idx'), models.Index(fields=['erledigt', 'datum'], name='stiftung_st_erledig_115235_idx')],
},
),
]

View File

@@ -0,0 +1,13 @@
# Generated by Django 5.0.6 on 2025-10-04 20:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('stiftung', '0039_stiftungskalendereintrag'),
]
operations = [
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2025-10-04 21:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('stiftung', '0040_add_calendar_model'),
]
operations = [
migrations.AlterField(
model_name='geschichteseite',
name='inhalt',
field=models.TextField(blank=True, help_text='Sie können Markdown verwenden: **fett**, *kursiv*, # Überschriften, [Links](URL), Listen, etc.', verbose_name='Inhalt (Markdown)'),
),
]

View File

@@ -2864,7 +2864,11 @@ class GeschichteSeite(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
titel = models.CharField(max_length=200, verbose_name="Titel") titel = models.CharField(max_length=200, verbose_name="Titel")
slug = models.SlugField(max_length=200, unique=True, verbose_name="URL-Slug") slug = models.SlugField(max_length=200, unique=True, verbose_name="URL-Slug")
inhalt = models.TextField(verbose_name="Inhalt") inhalt = models.TextField(
verbose_name="Inhalt (Markdown)",
blank=True,
help_text="Sie können Markdown verwenden: **fett**, *kursiv*, # Überschriften, [Links](URL), Listen, etc."
)
# Metadata # Metadata
erstellt_am = models.DateTimeField(auto_now_add=True, verbose_name="Erstellt am") erstellt_am = models.DateTimeField(auto_now_add=True, verbose_name="Erstellt am")
@@ -2940,3 +2944,124 @@ class GeschichteBild(models.Model):
def __str__(self): def __str__(self):
return f"{self.titel} ({self.seite.titel})" return f"{self.titel} ({self.seite.titel})"
class StiftungsKalenderEintrag(models.Model):
"""Custom calendar events for foundation management"""
KATEGORIE_CHOICES = [
('termin', 'Termin/Meeting'),
('zahlung', 'Zahlungserinnerung'),
('deadline', 'Frist/Deadline'),
('geburtstag', 'Geburtstag'),
('vertrag', 'Vertrag läuft aus'),
('pruefung', 'Prüfung/Nachweis'),
('sonstiges', 'Sonstiges'),
]
PRIORITAET_CHOICES = [
('niedrig', 'Niedrig'),
('normal', 'Normal'),
('hoch', 'Hoch'),
('kritisch', 'Kritisch'),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
titel = models.CharField(max_length=200, verbose_name="Titel")
beschreibung = models.TextField(blank=True, verbose_name="Beschreibung")
# Date and time
datum = models.DateField(verbose_name="Datum")
uhrzeit = models.TimeField(null=True, blank=True, verbose_name="Uhrzeit")
ganztags = models.BooleanField(default=True, verbose_name="Ganztägig")
# Categorization
kategorie = models.CharField(
max_length=20,
choices=KATEGORIE_CHOICES,
default='termin',
verbose_name="Kategorie"
)
prioritaet = models.CharField(
max_length=20,
choices=PRIORITAET_CHOICES,
default='normal',
verbose_name="Priorität"
)
# Links to related objects
destinataer = models.ForeignKey(
'Destinataer',
null=True,
blank=True,
on_delete=models.CASCADE,
verbose_name="Bezogener Destinatär"
)
verpachtung = models.ForeignKey(
'LandVerpachtung',
null=True,
blank=True,
on_delete=models.CASCADE,
verbose_name="Bezogene Verpachtung"
)
# Status and completion
erledigt = models.BooleanField(default=False, verbose_name="Erledigt")
erledigt_am = models.DateTimeField(null=True, blank=True, verbose_name="Erledigt am")
# Metadata
erstellt_von = models.CharField(
max_length=100,
null=True,
blank=True,
verbose_name="Erstellt von"
)
erstellt_am = models.DateTimeField(auto_now_add=True, verbose_name="Erstellt am")
aktualisiert_am = models.DateTimeField(auto_now=True, verbose_name="Aktualisiert am")
class Meta:
verbose_name = "Kalender Eintrag"
verbose_name_plural = "Kalender Einträge"
ordering = ['datum', 'uhrzeit']
indexes = [
models.Index(fields=['datum']),
models.Index(fields=['kategorie', 'datum']),
models.Index(fields=['erledigt', 'datum']),
]
def __str__(self):
return f"{self.datum}: {self.titel}"
def get_kategorie_icon(self):
icons = {
'termin': 'fas fa-calendar-alt',
'zahlung': 'fas fa-euro-sign',
'deadline': 'fas fa-exclamation-triangle',
'geburtstag': 'fas fa-birthday-cake',
'vertrag': 'fas fa-file-contract',
'pruefung': 'fas fa-clipboard-check',
'sonstiges': 'fas fa-calendar',
}
return icons.get(self.kategorie, 'fas fa-calendar')
def get_prioritaet_color(self):
colors = {
'niedrig': 'success',
'normal': 'primary',
'hoch': 'warning',
'kritisch': 'danger',
}
return colors.get(self.prioritaet, 'primary')
def is_overdue(self):
"""Check if event is overdue (past due and not completed)"""
if self.erledigt:
return False
return self.datum < timezone.now().date()
def is_upcoming(self, days=7):
"""Check if event is upcoming within specified days"""
if self.erledigt:
return False
today = timezone.now().date()
return today <= self.datum <= (today + timezone.timedelta(days=days))

View File

@@ -0,0 +1 @@
# Services package

View File

@@ -0,0 +1,269 @@
"""
Calendar service for aggregating date-based events from the foundation management system
"""
from datetime import date, timedelta
import calendar as cal
try:
from django.utils import timezone
except ImportError:
from datetime import datetime as timezone
timezone.now = datetime.now
try:
from stiftung.models import (
DestinataerUnterstuetzung,
LandVerpachtung,
Destinataer,
StiftungsKalenderEintrag
)
except ImportError:
# Fallback for when Django apps aren't ready
DestinataerUnterstuetzung = None
LandVerpachtung = None
Destinataer = None
StiftungsKalenderEintrag = None
class StiftungsKalenderService:
"""Service to aggregate calendar events from various models"""
def __init__(self):
self.today = timezone.now().date()
def get_calendar_events(self, start_date=None, end_date=None):
"""
Get custom calendar entries only within date range
Returns list of event objects
"""
if not start_date:
start_date = self.today
if not end_date:
end_date = start_date + timedelta(days=30)
return self._get_custom_calendar_events(start_date, end_date)
def get_all_events(self, start_date=None, end_date=None):
"""
Get all calendar events from all sources within date range
Returns list of event dictionaries
"""
if not start_date:
start_date = self.today
if not end_date:
end_date = start_date + timedelta(days=30)
events = []
# Add support payment due dates
events.extend(self.get_support_payment_events(start_date, end_date))
# Add lease expiration dates
events.extend(self.get_lease_events(start_date, end_date))
# Add birthdays
events.extend(self.get_birthday_events(start_date, end_date))
# Add custom calendar entries
events.extend(self.get_calendar_events(start_date, end_date))
# Sort by date
events.sort(key=lambda x: (x.date, getattr(x, 'time', '00:00')))
return events
def get_support_payment_events(self, start_date, end_date):
"""Get support payment due dates"""
if not DestinataerUnterstuetzung:
return []
events = []
payments = DestinataerUnterstuetzung.objects.filter(
faellig_am__range=[start_date, end_date],
status__in=['geplant', 'faellig']
).select_related('destinataer')
for payment in payments:
is_overdue = payment.is_overdue() if hasattr(payment, 'is_overdue') else False
class PaymentEvent:
def __init__(self, payment_obj, overdue):
self.id = f'payment_{payment_obj.id}'
self.title = f'Zahlung an {payment_obj.destinataer.get_full_name()}'
self.description = f'{payment_obj.betrag} - {payment_obj.beschreibung}'
self.date = payment_obj.faellig_am
self.time = None
self.category = 'zahlung'
self.category_display = 'Zahlung'
self.priority = 'hoch' if overdue else 'normal'
self.priority_display = 'Hoch' if overdue else 'Normal'
self.icon = 'fas fa-euro-sign'
self.color = 'danger' if overdue else 'warning'
self.source = 'payment'
self.overdue = overdue
self.completed = False
events.append(PaymentEvent(payment, is_overdue))
return events
def get_lease_events(self, start_date, end_date):
"""Get lease start/end dates"""
if not LandVerpachtung:
return []
events = []
# Lease expirations
leases = LandVerpachtung.objects.filter(
pachtende__range=[start_date, end_date],
status='aktiv'
).select_related('paechter', 'land')
for lease in leases:
# Check if expiring soon (within 90 days)
days_until_expiry = (lease.pachtende - self.today).days if lease.pachtende else 999
priority = 'hoch' if days_until_expiry <= 30 else 'mittel' if days_until_expiry <= 90 else 'normal'
class LeaseEvent:
def __init__(self, lease_obj, priority_val):
self.id = f'lease_end_{lease_obj.id}'
self.title = f'Pachtende: {lease_obj.paechter.get_full_name()}'
self.description = f'{lease_obj.land.bezeichnung} - {lease_obj.verpachtete_flaeche}'
self.date = lease_obj.pachtende
self.time = None
self.category = 'vertrag'
self.category_display = 'Vertrag'
self.priority = priority_val
self.priority_display = 'Hoch' if priority_val == 'hoch' else 'Mittel' if priority_val == 'mittel' else 'Normal'
self.icon = 'fas fa-file-contract'
self.color = 'danger' if priority_val == 'hoch' else 'warning' if priority_val == 'mittel' else 'info'
self.source = 'lease'
self.overdue = False
self.completed = False
events.append(LeaseEvent(lease, priority))
return events
def get_birthday_events(self, start_date, end_date):
"""Get birthdays within date range"""
if not Destinataer:
return []
events = []
# Get all destinatäre with birth dates
destinataere = Destinataer.objects.filter(
geburtsdatum__isnull=False
)
for destinataer in destinataere:
# Calculate birthday for each year in range
birth_date = destinataer.geburtsdatum
for year in range(start_date.year, end_date.year + 1):
try:
birthday_this_year = birth_date.replace(year=year)
if start_date <= birthday_this_year <= end_date:
age = year - birth_date.year
class BirthdayEvent:
def __init__(self, person, birthday_date, age_val):
self.id = f'birthday_{person.id}_{year}'
self.title = f'🎂 {person.get_full_name()}'
self.description = f'{age_val}. Geburtstag'
self.date = birthday_date
self.time = None
self.category = 'geburtstag'
self.category_display = 'Geburtstag'
self.priority = 'normal'
self.priority_display = 'Normal'
self.icon = 'fas fa-birthday-cake'
self.color = 'success'
self.source = 'birthday'
self.overdue = False
self.completed = False
events.append(BirthdayEvent(destinataer, birthday_this_year, age))
except ValueError:
# Handle leap year edge case (Feb 29)
pass
return events
def _get_custom_calendar_events(self, start_date, end_date):
"""Get custom calendar entries"""
if not StiftungsKalenderEintrag:
return []
events = []
calendar_entries = StiftungsKalenderEintrag.objects.filter(
datum__range=[start_date, end_date]
).select_related('destinataer', 'verpachtung')
for entry in calendar_entries:
class CustomEvent:
def __init__(self, entry_obj, today_date):
self.id = entry_obj.id
self.title = entry_obj.titel
self.description = entry_obj.beschreibung
self.date = entry_obj.datum
self.time = entry_obj.uhrzeit
self.category = entry_obj.kategorie
self.category_display = entry_obj.get_kategorie_display()
self.priority = entry_obj.prioritaet
self.priority_display = entry_obj.get_prioritaet_display()
self.icon = self._get_kategorie_icon(entry_obj.kategorie)
self.color = self._get_prioritaet_color(entry_obj.prioritaet)
self.source = 'custom'
self.completed = entry_obj.erledigt
self.overdue = entry_obj.datum < today_date if entry_obj.datum else False
def _get_kategorie_icon(self, kategorie):
icons = {
'termin': 'fas fa-calendar-alt',
'zahlung': 'fas fa-euro-sign',
'deadline': 'fas fa-exclamation-triangle',
'geburtstag': 'fas fa-birthday-cake',
'vertrag': 'fas fa-file-contract',
'pruefung': 'fas fa-search',
}
return icons.get(kategorie, 'fas fa-calendar')
def _get_prioritaet_color(self, prioritaet):
colors = {
'niedrig': 'secondary',
'normal': 'primary',
'mittel': 'warning',
'hoch': 'danger',
}
return colors.get(prioritaet, 'primary')
events.append(CustomEvent(entry, self.today))
return events
def get_upcoming_events(self, days=7):
"""Get upcoming events within specified days"""
end_date = self.today + timedelta(days=days)
return self.get_calendar_events(self.today, end_date)
def get_overdue_events(self):
"""Get overdue/past due events"""
events = self.get_calendar_events(
start_date=self.today - timedelta(days=30),
end_date=self.today - timedelta(days=1)
)
return [event for event in events if event.get('overdue', False)]
def get_events_for_month(self, year, month):
"""Get all events for a specific month"""
from calendar import monthrange
start_date = date(year, month, 1)
_, last_day = monthrange(year, month)
end_date = date(year, month, last_day)
return self.get_calendar_events(start_date, end_date)

166
app/stiftung/signals.py Normal file
View File

@@ -0,0 +1,166 @@
"""
Django signals for the Stiftung app.
Handles automatic # Check if a bank transaction already exists for this specific payment
existing_transaction = BankTransaction.objects.filter(
konto=instance.konto,
betrag=-instance.betrag, # Negative for outgoing payment
kommentare__contains=f'Unterstützung {instance.id}'
).first()
if existing_transaction:
print(f"⚠️ Transaction already exists for payment {instance.id} - skipping creation")
return
# Create a bank transaction for this payment
BankTransaction.objects.create(
konto=instance.konto,
datum=instance.ausgezahlt_am or timezone.now().date(),
valuta=instance.ausgezahlt_am or timezone.now().date(),
betrag=-instance.betrag, # Negative because it's an outgoing payment
waehrung='EUR',
verwendungszweck=f"Unterstützungszahlung: {instance.beschreibung or instance.destinataer.get_full_name()}",
empfaenger_zahlungspflichtiger=instance.empfaenger_name or instance.destinataer.get_full_name(),
iban_gegenpartei=instance.empfaenger_iban or '',
transaction_type='ueberweisung',
status='verified',
kommentare=f'Automatisch erstellt bei Markierung als ausgezahlt für Unterstützung {instance.id}',
referenz=f'PAY-{instance.id}', # Add unique reference to avoid conflicts
)model instances change.
"""
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from django.utils import timezone
from decimal import Decimal
from .models import DestinataerUnterstuetzung, BankTransaction
@receiver(pre_save, sender=DestinataerUnterstuetzung)
def unterstuetzung_pre_save(sender, instance, **kwargs):
"""Store the old status before saving to detect status changes"""
if instance.pk:
try:
old_instance = DestinataerUnterstuetzung.objects.get(pk=instance.pk)
instance._old_status = old_instance.status
except DestinataerUnterstuetzung.DoesNotExist:
instance._old_status = None
else:
instance._old_status = None
@receiver(post_save, sender=DestinataerUnterstuetzung)
def update_account_balance_on_payment(sender, instance, created, **kwargs):
"""
Update account balance when a payment is marked as paid (ausgezahlt).
Creates a corresponding bank transaction and updates the account balance.
Prevents duplicate transactions by checking if one already exists.
"""
# Only process if payment was just marked as paid
old_status = getattr(instance, '_old_status', None)
if instance.status == 'ausgezahlt' and old_status != 'ausgezahlt':
# Payment was just marked as paid
# Check if a transaction already exists for this payment to prevent duplicates
existing_transaction = BankTransaction.objects.filter(
konto=instance.konto,
betrag=-instance.betrag, # Negative for outgoing payment
kommentare__contains=f'Unterstützung {instance.id}'
).first()
if existing_transaction:
print(f"⚠️ Transaction already exists for payment {instance.id} to {instance.destinataer.get_full_name()}, skipping duplicate")
return
# Set the ausgezahlt_am date if not already set
if not instance.ausgezahlt_am:
instance.ausgezahlt_am = timezone.now().date()
# Avoid infinite recursion by updating without triggering signals
DestinataerUnterstuetzung.objects.filter(pk=instance.pk).update(
ausgezahlt_am=instance.ausgezahlt_am
)
# Check if a transaction already exists for this payment
existing_transaction = BankTransaction.objects.filter(
kommentare__contains=f'Unterstützung {instance.id}'
).first()
if not existing_transaction:
# Create a bank transaction for this payment
transaction = BankTransaction.objects.create(
konto=instance.konto,
datum=instance.ausgezahlt_am or timezone.now().date(),
valuta=instance.ausgezahlt_am or timezone.now().date(),
betrag=-instance.betrag, # Negative because it's an outgoing payment
waehrung='EUR',
verwendungszweck=f"Unterstützungszahlung: {instance.beschreibung or instance.destinataer.get_full_name()}",
empfaenger_zahlungspflichtiger=instance.empfaenger_name or instance.destinataer.get_full_name(),
iban_gegenpartei=instance.empfaenger_iban or '',
transaction_type='ueberweisung',
status='verified',
referenz=f'PAY-{instance.id}', # Unique reference to prevent duplicates
kommentare=f'Automatisch erstellt bei Markierung als ausgezahlt für Unterstützung {instance.id}',
)
# Update account balance only for new transactions
instance.konto.saldo -= instance.betrag
instance.konto.saldo_datum = instance.ausgezahlt_am or timezone.now().date()
instance.konto.save()
print(f"✅ Account balance updated: {instance.konto.kontoname} - €{instance.betrag} (Payment to {instance.destinataer.get_full_name()}) - Transaction {transaction.id}")
else:
transaction = existing_transaction
print(f" Transaction already exists for payment {instance.id}, balance not modified")
# Handle reversal if payment is changed from paid back to unpaid
elif old_status == 'ausgezahlt' and instance.status != 'ausgezahlt':
# Payment was unmarked as paid - reverse the transaction
# Find and delete the corresponding bank transaction
try:
# Look for the transaction created for this payment
transaction = BankTransaction.objects.filter(
konto=instance.konto,
betrag=-instance.betrag,
kommentare__contains=f'Unterstützung {instance.id}'
).first()
if transaction:
transaction.delete()
# Reverse the account balance update
instance.konto.saldo += instance.betrag
instance.konto.saldo_datum = timezone.now().date()
instance.konto.save()
print(f"✅ Account balance reversed: {instance.konto.kontoname} + €{instance.betrag} (Payment reversal for {instance.destinataer.get_full_name()})")
else:
print(f"⚠️ No transaction found to reverse for payment {instance.id}")
except Exception as e:
print(f"⚠️ Error reversing payment transaction: {e}")
# Clear the ausgezahlt_am date
if instance.ausgezahlt_am:
# Update without triggering signals
DestinataerUnterstuetzung.objects.filter(pk=instance.pk).update(
ausgezahlt_am=None
)
@receiver(post_save, sender=BankTransaction)
def update_account_balance_on_transaction(sender, instance, created, **kwargs):
"""
Update account balance when a new bank transaction is imported or created.
Only update if the transaction has a saldo_nach_buchung value or if it's manually created.
"""
if created and instance.status in ['verified', 'imported']:
# If the transaction has a balance after booking, use that
if instance.saldo_nach_buchung is not None:
instance.konto.saldo = instance.saldo_nach_buchung
instance.konto.saldo_datum = instance.datum
instance.konto.save()
else:
# Otherwise, calculate the new balance
instance.konto.saldo += instance.betrag
instance.konto.saldo_datum = instance.datum
instance.konto.save()

View File

@@ -32,3 +32,19 @@ def help_box(page_key, user=None):
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"""
return HelpBox.get_help_for_page(page_key) is not None return HelpBox.get_help_for_page(page_key) is not None
@register.filter
def markdown_to_html(text):
"""Konvertiere Markdown-Text zu HTML"""
if not text:
return ""
md = markdown.Markdown(extensions=[
"nl2br",
"fenced_code",
"tables",
"toc",
"codehilite"
])
return mark_safe(md.convert(text))

View File

@@ -5,10 +5,10 @@ from . import views
app_name = "stiftung" app_name = "stiftung"
urlpatterns = [ urlpatterns = [
# Dashboard (Startseite) # Home - Main landing page after login
path("", views.dashboard, name="dashboard"), path("", views.home, name="home"),
# Home (für Kompatibilität mit bestehenden Templates) # Dashboard (detailed view)
path("home/", views.home, name="home"), path("dashboard/", views.dashboard, name="dashboard"),
# 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"),
@@ -391,4 +391,14 @@ urlpatterns = [
path("geschichte/<slug:slug>/", views.geschichte_detail, name="geschichte_detail"), path("geschichte/<slug:slug>/", views.geschichte_detail, name="geschichte_detail"),
path("geschichte/<slug:slug>/bearbeiten/", views.geschichte_edit, name="geschichte_edit"), path("geschichte/<slug:slug>/bearbeiten/", views.geschichte_edit, name="geschichte_edit"),
path("geschichte/<slug:slug>/bild-upload/", views.geschichte_bild_upload, name="geschichte_bild_upload"), path("geschichte/<slug:slug>/bild-upload/", views.geschichte_bild_upload, name="geschichte_bild_upload"),
path("geschichte/<slug:slug>/bild/<uuid:bild_id>/loeschen/", views.geschichte_bild_delete, name="geschichte_bild_delete"),
# Kalender URLs
path("kalender/", views.kalender_view, name="kalender"),
path("kalender/admin/", views.kalender_admin, name="kalender_admin"),
path("kalender/neu/", views.kalender_create, name="kalender_create"),
path("kalender/<uuid:pk>/", views.kalender_detail, name="kalender_detail"),
path("kalender/<uuid:pk>/bearbeiten/", views.kalender_edit, name="kalender_edit"),
path("kalender/<uuid:pk>/loeschen/", views.kalender_delete, name="kalender_delete"),
path("kalender/api/events/", views.kalender_api_events, name="kalender_api_events"),
] ]

View File

@@ -3,7 +3,7 @@ import io
import json import json
import os import os
import time import time
from datetime import datetime, timedelta from datetime import datetime, timedelta, date
from decimal import Decimal from decimal import Decimal
import qrcode import qrcode
@@ -225,11 +225,38 @@ from .forms import (DestinataerForm, DestinataerNotizForm,
def home(request): def home(request):
"""Home page for the Stiftungsverwaltung application""" """Home page for the Stiftungsverwaltung application"""
return render( from stiftung.services.calendar_service import StiftungsKalenderService
request,
"stiftung/home.html", # Get upcoming events for the calendar widget
{"title": "Stiftungsverwaltung", "description": "Foundation Management System"}, calendar_service = StiftungsKalenderService()
)
# Get all events for the next 14 days
from datetime import timedelta
today = timezone.now().date()
end_date = today + timedelta(days=14)
all_events = calendar_service.get_all_events(today, end_date)
# Filter for upcoming and overdue
upcoming_events = [e for e in all_events if not getattr(e, 'overdue', False)]
overdue_events = [e for e in all_events if getattr(e, 'overdue', False)]
# Get current month events for mini calendar
from calendar import monthrange
_, last_day = monthrange(today.year, today.month)
month_start = today.replace(day=1)
month_end = today.replace(day=last_day)
current_month_events = calendar_service.get_all_events(month_start, month_end)
context = {
"title": "Stiftungsverwaltung",
"description": "Foundation Management System",
"upcoming_events": upcoming_events[:5], # Show only 5 upcoming events
"overdue_events": overdue_events[:3], # Show only 3 overdue events
"current_month_events": current_month_events,
"today": today,
}
return render(request, "stiftung/home.html", context)
@login_required @login_required
@@ -8152,3 +8179,412 @@ def geschichte_bild_upload(request, slug):
} }
return render(request, 'stiftung/geschichte/bild_form.html', context) return render(request, 'stiftung/geschichte/bild_form.html', context)
@login_required
def geschichte_bild_delete(request, slug, bild_id):
"""Delete an image from a history page"""
seite = get_object_or_404(GeschichteSeite, slug=slug)
bild = get_object_or_404(GeschichteBild, id=bild_id, seite=seite)
if not request.user.has_perm('stiftung.delete_geschichtebild'):
messages.error(request, 'Sie haben keine Berechtigung, Bilder zu löschen.')
return redirect('stiftung:geschichte_detail', slug=slug)
if request.method == 'POST':
bild_titel = bild.titel
bild.delete()
messages.success(request, f'Bild "{bild_titel}" wurde erfolgreich gelöscht.')
return redirect('stiftung:geschichte_detail', slug=slug)
context = {
'bild': bild,
'seite': seite,
'title': f'Bild löschen: {bild.titel}'
}
return render(request, 'stiftung/geschichte/bild_delete.html', context)
# Calendar Views
@login_required
def kalender_view(request):
"""Main calendar view with different view types"""
from stiftung.services.calendar_service import StiftungsKalenderService
import calendar as cal
calendar_service = StiftungsKalenderService()
# Get current date and view parameters
today = timezone.now().date()
view_type = request.GET.get('view', 'month') # month, week, list, agenda
year = int(request.GET.get('year', today.year))
month = int(request.GET.get('month', today.month))
# Calculate date ranges based on view type
if view_type == 'month':
# Get events for the entire month
start_date = date(year, month, 1)
_, last_day = cal.monthrange(year, month)
end_date = date(year, month, last_day)
title_suffix = f"{cal.month_name[month]} {year}"
elif view_type == 'week':
# Get current week
week_start = today - timedelta(days=today.weekday())
start_date = week_start
end_date = week_start + timedelta(days=6)
title_suffix = f"Woche vom {start_date.strftime('%d.%m')} - {end_date.strftime('%d.%m.%Y')}"
elif view_type == 'agenda':
# Next 30 days
start_date = today
end_date = today + timedelta(days=30)
title_suffix = "Nächste 30 Tage"
else: # list view
# Next 90 days
start_date = today
end_date = today + timedelta(days=90)
title_suffix = "Liste (nächste 90 Tage)"
# Get events for the date range
events = calendar_service.get_all_events(start_date, end_date)
# Generate calendar grid for month view
calendar_grid = None
if view_type == 'month':
calendar_grid = []
first_day = date(year, month, 1)
month_cal = cal.monthcalendar(year, month)
for week in month_cal:
week_data = []
for day in week:
if day == 0:
week_data.append(None)
else:
day_date = date(year, month, day)
day_events = [e for e in events if e.date == day_date]
week_data.append({
'day': day,
'date': day_date,
'is_today': day_date == today,
'events': day_events[:3], # Show max 3 events per day
'event_count': len(day_events)
})
calendar_grid.append(week_data)
# Navigation dates for month view
if month > 1:
prev_month = month - 1
prev_year = year
else:
prev_month = 12
prev_year = year - 1
if month < 12:
next_month = month + 1
next_year = year
else:
next_month = 1
next_year = year + 1
context = {
'title': f'Kalender - {title_suffix}',
'events': events,
'calendar_grid': calendar_grid,
'view_type': view_type,
'year': year,
'month': month,
'today': today,
'start_date': start_date,
'end_date': end_date,
'prev_year': prev_year,
'prev_month': prev_month,
'next_year': next_year,
'next_month': next_month,
'month_name': cal.month_name[month],
'weekdays': ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'],
}
# Choose template based on view type
if view_type == 'month':
template = 'stiftung/kalender/month_view.html'
elif view_type == 'week':
template = 'stiftung/kalender/week_view.html'
elif view_type == 'agenda':
template = 'stiftung/kalender/agenda_view.html'
else:
template = 'stiftung/kalender/list_view.html'
return render(request, template, context)
@login_required
def kalender_create(request):
"""Create new calendar event"""
from stiftung.models import StiftungsKalenderEintrag
if request.method == 'POST':
# Simple form handling - you can enhance this with Django forms
titel = request.POST.get('titel')
beschreibung = request.POST.get('beschreibung', '')
datum = request.POST.get('datum')
kategorie = request.POST.get('kategorie', 'termin')
prioritaet = request.POST.get('prioritaet', 'normal')
if titel and datum:
zeit_str = request.POST.get('zeit')
uhrzeit = zeit_str if zeit_str else None
ganztags = not bool(zeit_str)
StiftungsKalenderEintrag.objects.create(
titel=titel,
beschreibung=beschreibung,
datum=datum,
uhrzeit=uhrzeit,
ganztags=ganztags,
kategorie=kategorie,
prioritaet=prioritaet,
erstellt_von=request.user.username
)
messages.success(request, 'Kalendereintrag wurde erfolgreich erstellt.')
return redirect('stiftung:kalender')
else:
messages.error(request, 'Titel und Datum sind erforderlich.')
context = {
'title': 'Neuer Kalendereintrag',
}
return render(request, 'stiftung/kalender/create.html', context)
@login_required
def kalender_detail(request, pk):
"""Calendar event detail view"""
from stiftung.models import StiftungsKalenderEintrag
event = get_object_or_404(StiftungsKalenderEintrag, pk=pk)
context = {
'title': f'Kalendereintrag: {event.titel}',
'event': event,
}
return render(request, 'stiftung/kalender/detail.html', context)
@login_required
def kalender_edit(request, pk):
"""Edit calendar event"""
from stiftung.models import StiftungsKalenderEintrag
event = get_object_or_404(StiftungsKalenderEintrag, pk=pk)
if request.method == 'POST':
event.titel = request.POST.get('titel', event.titel)
event.beschreibung = request.POST.get('beschreibung', event.beschreibung)
event.datum = request.POST.get('datum', event.datum)
zeit_str = request.POST.get('zeit')
if zeit_str:
event.uhrzeit = zeit_str
event.ganztags = False
else:
event.uhrzeit = None
event.ganztags = True
event.kategorie = request.POST.get('kategorie', event.kategorie)
event.prioritaet = request.POST.get('prioritaet', event.prioritaet)
event.erledigt = 'erledigt' in request.POST
event.save()
messages.success(request, 'Kalendereintrag wurde aktualisiert.')
return redirect('stiftung:kalender_detail', pk=pk)
context = {
'title': f'Bearbeiten: {event.titel}',
'event': event,
}
return render(request, 'stiftung/kalender/edit.html', context)
@login_required
def kalender_delete(request, pk):
"""Delete calendar event"""
from stiftung.models import StiftungsKalenderEintrag
event = get_object_or_404(StiftungsKalenderEintrag, pk=pk)
if request.method == 'POST':
event_titel = event.titel
event.delete()
messages.success(request, f'Kalendereintrag "{event_titel}" wurde gelöscht.')
return redirect('stiftung:kalender')
context = {
'title': f'Löschen: {event.titel}',
'event': event,
}
return render(request, 'stiftung/kalender/delete_confirm.html', context)
@login_required
def kalender_admin(request):
"""Calendar administration with event sources and management"""
from stiftung.models import StiftungsKalenderEintrag
from stiftung.services.calendar_service import StiftungsKalenderService
# Get filter parameters
show_custom = request.GET.get('show_custom', 'true') == 'true'
show_payments = request.GET.get('show_payments', 'true') == 'true'
show_leases = request.GET.get('show_leases', 'true') == 'true'
show_birthdays = request.GET.get('show_birthdays', 'true') == 'true'
category_filter = request.GET.get('category', '')
priority_filter = request.GET.get('priority', '')
# Initialize calendar service
calendar_service = StiftungsKalenderService()
# Get events based on filters
from datetime import date, timedelta
start_date = date.today() - timedelta(days=30)
end_date = date.today() + timedelta(days=90)
all_events = []
# Custom calendar entries
if show_custom:
custom_events = calendar_service.get_calendar_events(start_date, end_date)
all_events.extend(custom_events)
# Payment events
if show_payments:
payment_events = calendar_service.get_support_payment_events(start_date, end_date)
all_events.extend(payment_events)
# Lease events
if show_leases:
lease_events = calendar_service.get_lease_events(start_date, end_date)
all_events.extend(lease_events)
# Birthday events
if show_birthdays:
birthday_events = calendar_service.get_birthday_events(start_date, end_date)
all_events.extend(birthday_events)
# Filter by category and priority if specified
if category_filter:
all_events = [e for e in all_events if getattr(e, 'category', '') == category_filter]
if priority_filter:
all_events = [e for e in all_events if getattr(e, 'priority', '') == priority_filter]
# Sort events by date
all_events.sort(key=lambda x: x.date)
# Get statistics
custom_count = StiftungsKalenderEintrag.objects.count()
total_events = len(all_events)
# Event source statistics
stats = {
'custom_events': len([e for e in all_events if getattr(e, 'source', '') == 'custom']),
'payment_events': len([e for e in all_events if getattr(e, 'source', '') == 'payment']),
'lease_events': len([e for e in all_events if getattr(e, 'source', '') == 'lease']),
'birthday_events': len([e for e in all_events if getattr(e, 'source', '') == 'birthday']),
'total_events': total_events,
'custom_count': custom_count,
}
context = {
'title': 'Kalender Administration',
'events': all_events,
'stats': stats,
'show_custom': show_custom,
'show_payments': show_payments,
'show_leases': show_leases,
'show_birthdays': show_birthdays,
'category_filter': category_filter,
'priority_filter': priority_filter,
'categories': StiftungsKalenderEintrag.KATEGORIE_CHOICES,
'priorities': StiftungsKalenderEintrag.PRIORITAET_CHOICES,
}
return render(request, 'stiftung/kalender/admin.html', context)
context = {
'title': f'Löschen: {event.titel}',
'event': event,
}
return render(request, 'stiftung/kalender/delete.html', context)
@login_required
def kalender_api_events(request):
"""API endpoint for calendar events (JSON)"""
from django.http import JsonResponse
from stiftung.services.calendar_service import StiftungsKalenderService
from datetime import datetime
calendar_service = StiftungsKalenderService()
# Get date range from request
start_date = request.GET.get('start')
end_date = request.GET.get('end')
if start_date and end_date:
try:
start_date = datetime.strptime(start_date, '%Y-%m-%d').date()
end_date = datetime.strptime(end_date, '%Y-%m-%d').date()
except ValueError:
return JsonResponse({'error': 'Invalid date format'}, status=400)
events = calendar_service.get_all_events(start_date, end_date)
else:
events = calendar_service.get_all_events()
# Convert to FullCalendar format
calendar_events = []
for event in events:
calendar_events.append({
'id': getattr(event, 'id', str(event.title)),
'title': event.title,
'start': event.date.strftime('%Y-%m-%d'),
'description': getattr(event, 'description', ''),
'className': f"event-{event.category}",
'backgroundColor': f"var(--bs-{event.color})",
'borderColor': f"var(--bs-{event.color})",
})
return JsonResponse(calendar_events, safe=False)
# Calendar Views
@login_required
def kalender_view(request):
"""Full calendar view with all events"""
from stiftung.services.calendar_service import StiftungsKalenderService
calendar_service = StiftungsKalenderService()
# Get current month events by default
today = timezone.now().date()
events = calendar_service.get_events_for_month(today.year, today.month)
context = {
'events': events,
'title': 'Stiftungskalender',
'current_month': today.strftime('%B %Y'),
}
return render(request, 'stiftung/kalender/kalender.html', context)
context = {
'title': 'Kalendereintrag löschen'
}
return render(request, 'stiftung/kalender/delete.html', context)

View File

@@ -525,14 +525,19 @@
<ul class="navbar-nav me-auto"> <ul class="navbar-nav me-auto">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'stiftung:home' %}"> <a class="nav-link" href="{% url 'stiftung:home' %}">
<i class="fas fa-home me-1"></i>Home
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'stiftung:dashboard' %}">
<i class="fas fa-tachometer-alt me-1"></i>Dashboard <i class="fas fa-tachometer-alt me-1"></i>Dashboard
</a> </a>
</li> </li>
<!-- Menschen & Finanzen --> <!-- Destinatäre -->
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="personenDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="#" id="personenDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-users me-1"></i>Menschen & Finanzen <i class="fas fa-users me-1"></i>Destinatäre
</a> </a>
<ul class="dropdown-menu" aria-labelledby="personenDropdown"> <ul class="dropdown-menu" aria-labelledby="personenDropdown">
<li><h6 class="dropdown-header">Destinatäre</h6></li> <li><h6 class="dropdown-header">Destinatäre</h6></li>
@@ -558,18 +563,13 @@
<li><a class="dropdown-item" href="{% url 'stiftung:unterstuetzung_create' %}"> <li><a class="dropdown-item" href="{% url 'stiftung:unterstuetzung_create' %}">
<i class="fas fa-plus me-2"></i>Neue Unterstützung <i class="fas fa-plus me-2"></i>Neue Unterstützung
</a></li> </a></li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">Pächter</h6></li>
<li><a class="dropdown-item" href="{% url 'stiftung:paechter_list' %}">
<i class="fas fa-user-tie me-2"></i>Alle Pächter
</a></li>
</ul> </ul>
</li> </li>
<!-- Immobilien & Land --> <!-- Land & Pacht -->
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="immobilienDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="#" id="immobilienDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-tree me-1"></i>Immobilien & Land <i class="fas fa-tree me-1"></i>Land & Pacht
</a> </a>
<ul class="dropdown-menu" aria-labelledby="immobilienDropdown"> <ul class="dropdown-menu" aria-labelledby="immobilienDropdown">
<li><h6 class="dropdown-header">Ländereien</h6></li> <li><h6 class="dropdown-header">Ländereien</h6></li>
@@ -595,6 +595,11 @@
<li><a class="dropdown-item" href="{% url 'stiftung:land_abrechnung_create' %}"> <li><a class="dropdown-item" href="{% url 'stiftung:land_abrechnung_create' %}">
<i class="fas fa-plus me-2"></i>Neue Abrechnung <i class="fas fa-plus me-2"></i>Neue Abrechnung
</a></li> </a></li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">Pächter</h6></li>
<li><a class="dropdown-item" href="{% url 'stiftung:paechter_list' %}">
<i class="fas fa-user-tie me-2"></i>Alle Pächter
</a></li>
</ul> </ul>
</li> </li>
@@ -610,6 +615,31 @@
<li><a class="dropdown-item" href="{% url 'stiftung:geschaeftsfuehrung' %}"> <li><a class="dropdown-item" href="{% url 'stiftung:geschaeftsfuehrung' %}">
<i class="fas fa-briefcase me-2"></i>Geschäftsführung <i class="fas fa-briefcase me-2"></i>Geschäftsführung
</a></li> </a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="{% url 'stiftung:administration' %}">
<i class="fas fa-cogs me-2"></i>Administration
</a></li>
</ul>
</li>
<!-- Kalender -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="kalenderDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-calendar-alt me-1"></i>Kalender
</a>
<ul class="dropdown-menu" aria-labelledby="kalenderDropdown">
<li><h6 class="dropdown-header">Ansichten</h6></li>
<li><a class="dropdown-item" href="{% url 'stiftung:kalender' %}">
<i class="fas fa-calendar me-2"></i>Kalender anzeigen
</a></li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">Verwaltung</h6></li>
<li><a class="dropdown-item" href="{% url 'stiftung:kalender_admin' %}">
<i class="fas fa-cogs me-2"></i>Kalender Administration
</a></li>
<li><a class="dropdown-item" href="{% url 'stiftung:kalender_create' %}">
<i class="fas fa-plus me-2"></i>Neuer Termin
</a></li>
</ul> </ul>
</li> </li>
@@ -619,11 +649,6 @@
<i class="fas fa-book-open me-1"></i>Geschichte <i class="fas fa-book-open me-1"></i>Geschichte
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="{% url 'stiftung:administration' %}">
<i class="fas fa-cogs me-1"></i>Administration
</a>
</li>
<!-- User Menu --> <!-- User Menu -->
{% if user.is_authenticated %} {% if user.is_authenticated %}
@@ -672,9 +697,10 @@
</a> </a>
</li> </li>
{% endif %} {% endif %}
</div> </ul>
</div> </div>
</nav> </div>
</nav>
<!-- Content Wrapper --> <!-- Content Wrapper -->
<div id="content-wrapper" class="d-flex flex-column"> <div id="content-wrapper" class="d-flex flex-column">

View File

@@ -24,7 +24,7 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="text-muted mb-4"> <p class="text-muted mb-4">
Generieren Sie detaillierte Jahresberichte mit allen wichtigen Informationen zu Personen, Generieren Sie detaillierte Jahresberichte mit allen wichtigen Informationen zu Destinatären,
Förderungen und Ländereien. Förderungen und Ländereien.
</p> </p>
@@ -83,8 +83,8 @@
<div class="card bg-primary text-white"> <div class="card bg-primary text-white">
<div class="card-body text-center"> <div class="card-body text-center">
<i class="fas fa-users fa-2x mb-2"></i> <i class="fas fa-users fa-2x mb-2"></i>
<h5 class="card-title">Personen</h5> <h5 class="card-title">Destinatäre</h5>
<h3 class="card-text">{{ total_persons|default:"0" }}</h3> <h3 class="card-text">{{ total_destinataere|default:"0" }}</h3>
</div> </div>
</div> </div>
</div> </div>
@@ -129,8 +129,8 @@
<div class="mt-4"> <div class="mt-4">
<h6 class="text-primary">Schnellzugriff:</h6> <h6 class="text-primary">Schnellzugriff:</h6>
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<a href="{% url 'stiftung:person_list' %}" class="btn btn-outline-primary"> <a href="{% url 'stiftung:destinataer_list' %}" class="btn btn-outline-primary">
<i class="fas fa-users me-2"></i>Alle Personen anzeigen <i class="fas fa-users me-2"></i>Alle Destinatäre anzeigen
</a> </a>
<a href="{% url 'stiftung:foerderung_list' %}" class="btn btn-outline-success"> <a href="{% url 'stiftung:foerderung_list' %}" class="btn btn-outline-success">
<i class="fas fa-gift me-2"></i>Alle Förderungen anzeigen <i class="fas fa-gift me-2"></i>Alle Förderungen anzeigen

View File

@@ -0,0 +1,66 @@
{% extends 'base.html' %}
{% load help_tags %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'stiftung:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'stiftung:geschichte_list' %}">Geschichte</a></li>
<li class="breadcrumb-item"><a href="{{ seite.get_absolute_url }}">{{ seite.titel }}</a></li>
<li class="breadcrumb-item active" aria-current="page">Bild löschen</li>
</ol>
</nav>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow">
<div class="card-header bg-danger text-white">
<h5 class="mb-0">
<i class="fas fa-trash me-2"></i>{{ title }}
</h5>
</div>
<div class="card-body">
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Achtung!</strong> Dieser Vorgang kann nicht rückgängig gemacht werden.
</div>
<div class="row">
<div class="col-md-4">
<img src="{{ bild.bild.url }}" alt="{{ bild.alt_text }}" class="img-fluid rounded">
</div>
<div class="col-md-8">
<h6>Bild-Details:</h6>
<ul class="list-unstyled">
<li><strong>Titel:</strong> {{ bild.titel }}</li>
<li><strong>Beschreibung:</strong> {{ bild.beschreibung|default:"Keine Beschreibung" }}</li>
<li><strong>Hochgeladen:</strong> {{ bild.hochgeladen_am|date:"d.m.Y H:i" }}</li>
{% if bild.hochgeladen_von %}
<li><strong>Hochgeladen von:</strong> {{ bild.hochgeladen_von.get_full_name|default:bild.hochgeladen_von.username }}</li>
{% endif %}
</ul>
</div>
</div>
<hr>
<p>Möchten Sie dieses Bild wirklich löschen?</p>
<form method="post">
{% csrf_token %}
<div class="d-flex justify-content-between">
<a href="{{ seite.get_absolute_url }}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-1"></i>Abbrechen
</a>
<button type="submit" class="btn btn-danger">
<i class="fas fa-trash me-1"></i>Endgültig löschen
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -67,7 +67,7 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="geschichte-content"> <div class="geschichte-content">
{{ seite.inhalt|linebreaks }} {{ seite.inhalt|markdown_to_html }}
</div> </div>
{% if bilder %} {% if bilder %}
@@ -75,13 +75,22 @@
<h4><i class="fas fa-images me-2"></i>Bildergalerie</h4> <h4><i class="fas fa-images me-2"></i>Bildergalerie</h4>
{% for bild in bilder %} {% for bild in bilder %}
<div class="geschichte-bild"> <div class="geschichte-bild">
<img src="{{ bild.bild.url }}" alt="{{ bild.alt_text|default:bild.titel }}" class="img-fluid"> <div class="d-flex justify-content-between align-items-start mb-2">
<div class="bild-beschreibung"> <h6 class="mb-0">{{ bild.titel }}</h6>
<strong>{{ bild.titel }}</strong> {% if perms.stiftung.delete_geschichtebild %}
{% if bild.beschreibung %} <a href="{% url 'stiftung:geschichte_bild_delete' slug=seite.slug bild_id=bild.id %}"
<br>{{ bild.beschreibung }} class="btn btn-outline-danger btn-sm"
title="Bild löschen">
<i class="fas fa-trash"></i>
</a>
{% endif %} {% endif %}
</div> </div>
<img src="{{ bild.bild.url }}" alt="{{ bild.alt_text|default:bild.titel }}" class="img-fluid">
{% if bild.beschreibung %}
<div class="bild-beschreibung mt-2">
{{ bild.beschreibung }}
</div>
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>

View File

@@ -4,19 +4,37 @@
{% block title %}{{ title }}{% endblock %} {% block title %}{{ title }}{% endblock %}
{% block extra_css %} {% block extra_css %}
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde@2.18.0/dist/easymde.min.css">
<style> <style>
.ql-editor { .EasyMDEContainer {
margin-bottom: 1rem;
}
.EasyMDEContainer .editor-toolbar {
border-color: #dee2e6;
background: #f8f9fa;
}
.EasyMDEContainer .CodeMirror {
border-color: #dee2e6;
font-family: inherit;
font-size: 1rem;
min-height: 300px; min-height: 300px;
} }
.ql-toolbar {
border-top-left-radius: 0.375rem; .EasyMDEContainer .editor-preview,
border-top-right-radius: 0.375rem; .EasyMDEContainer .editor-preview-side {
}
.ql-container {
border-bottom-left-radius: 0.375rem;
border-bottom-right-radius: 0.375rem;
font-family: inherit; font-family: inherit;
font-size: 1rem;
line-height: 1.5;
}
.editor-help {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
margin-top: 0.5rem;
} }
</style> </style>
{% endblock %} {% endblock %}
@@ -75,13 +93,37 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">{{ form.inhalt.label }}</label> <label class="form-label" for="{{ form.inhalt.id_for_label }}">{{ form.inhalt.label }}</label>
<div id="quill-editor" style="height: 400px;"></div>
<textarea name="inhalt" id="id_inhalt" style="display: none;">{{ form.inhalt.value|default:"" }}</textarea> <!-- Markdown Editor -->
{{ form.inhalt }}
{% if form.inhalt.errors %} {% if form.inhalt.errors %}
<div class="text-danger">{{ form.inhalt.errors }}</div> <div class="text-danger">{{ form.inhalt.errors }}</div>
{% endif %} {% endif %}
<div class="form-text">{{ form.inhalt.help_text }}</div> <div class="form-text">{{ form.inhalt.help_text }}</div>
<div class="editor-help mt-2">
<h6><i class="fas fa-info-circle me-1"></i>Markdown-Hilfe</h6>
<div class="row">
<div class="col-md-6">
<small>
<strong>Formatierung:</strong><br>
<code>**Fett**</code><strong>Fett</strong><br>
<code>*Kursiv*</code><em>Kursiv</em><br>
<code>~~Durchgestrichen~~</code><del>Durchgestrichen</del>
</small>
</div>
<div class="col-md-6">
<small>
<strong>Überschriften:</strong><br>
<code># Überschrift 1</code><br>
<code>## Überschrift 2</code><br>
<code>### Überschrift 3</code>
</small>
</div>
</div>
</div>
</div> </div>
<div class="mb-3 form-check"> <div class="mb-3 form-check">
@@ -110,18 +152,26 @@
<div class="col-lg-4"> <div class="col-lg-4">
<div class="card"> <div class="card">
<div class="card-header bg-info text-white"> <div class="card-header bg-info text-white">
<h6 class="mb-0"><i class="fas fa-info-circle me-2"></i>Rich Text Editor</h6> <h6 class="mb-0"><i class="fas fa-markdown me-2"></i>Markdown Editor</h6>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="small">Der Editor unterstützt:</p> <p class="small">Der Markdown-Editor unterstützt:</p>
<ul class="small"> <ul class="small">
<li>Formatierung (Fett, Kursiv, Unterstrichen)</li> <li>Live-Vorschau beim Schreiben</li>
<li>Überschriften (H1, H2, H3)</li> <li>Toolbar für häufige Formatierungen</li>
<li>Listen (nummeriert und Aufzählungen)</li> <li>Vollbild-Modus für fokussiertes Schreiben</li>
<li>Links</li> <li>Automatisches Speichern im Browser</li>
<li>Bilder (über separaten Upload)</li>
</ul> </ul>
<hr> <hr>
<h6 class="small">Häufige Markdown-Syntax:</h6>
<div class="small">
<code>- Listen-Element</code><br>
<code>1. Nummerierte Liste</code><br>
<code>[Link Text](URL)</code><br>
<code>&gt; Zitat</code><br>
<code>`Code`</code>
</div>
<hr>
<p class="small text-muted"> <p class="small text-muted">
Tipp: Verwenden Sie die Bild-Upload-Funktion, um Bilder zur Seite hinzuzufügen. Tipp: Verwenden Sie die Bild-Upload-Funktion, um Bilder zur Seite hinzuzufügen.
</p> </p>
@@ -132,46 +182,88 @@
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
<script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/easymde@2.18.0/dist/easymde.min.js"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Initialize Quill editor console.log('Initializing EasyMDE markdown editor...');
var quill = new Quill('#quill-editor', {
theme: 'snow', var textarea = document.getElementById('id_inhalt');
modules: {
if (textarea) {
var easyMDE = new EasyMDE({
element: textarea,
autofocus: false,
autosave: {
enabled: true,
delay: 1000,
uniqueId: 'geschichte_' + (window.location.pathname.match(/\/(\w+)\//) || ['', 'new'])[1]
},
spellChecker: false,
toolbar: [ toolbar: [
[{ 'header': [1, 2, 3, false] }], 'bold', 'italic', 'strikethrough', '|',
['bold', 'italic', 'underline', 'strike'], 'heading-1', 'heading-2', 'heading-3', '|',
[{ 'list': 'ordered'}, { 'list': 'bullet' }], 'unordered-list', 'ordered-list', '|',
[{ 'color': [] }, { 'background': [] }], 'link', 'quote', 'code', '|',
['link'], 'preview', 'side-by-side', 'fullscreen', '|',
['clean'] 'guide'
] ],
}, placeholder: 'Schreiben Sie hier den Inhalt der Geschichtsseite...\n\nSie können Markdown verwenden für:\n- **Fettdruck**\n- *Kursiv*\n- # Überschriften\n- [Links](URL)\n- Listen und vieles mehr',
placeholder: 'Schreiben Sie hier den Inhalt der Geschichtsseite...' renderingConfig: {
}); singleLineBreaks: false,
codeSyntaxHighlighting: false
// Load existing content },
var existingContent = document.getElementById('id_inhalt').value; previewRender: function(plainText) {
if (existingContent) { // Simple markdown to HTML conversion for preview
quill.root.innerHTML = existingContent; return plainText
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
.replace(/^\> (.*$)/gim, '<blockquote>$1</blockquote>')
.replace(/\*\*(.*)\*\*/gim, '<strong>$1</strong>')
.replace(/\*(.*)\*/gim, '<em>$1</em>')
.replace(/~~(.*)~~/gim, '<del>$1</del>')
.replace(/`([^`]*)`/gim, '<code>$1</code>')
.replace(/\[([^\]]*)\]\(([^)]*)\)/gim, '<a href="$2">$1</a>')
.replace(/^\* (.*$)/gim, '<li>$1</li>')
.replace(/^\d+\. (.*$)/gim, '<li>$1</li>')
.replace(/\n/gim, '<br>');
},
status: ['autosave', 'lines', 'words', 'cursor'],
insertTexts: {
horizontalRule: ["", "\n\n-----\n\n"],
image: ["![", "](http://)"],
link: ["[", "](http://)"],
table: ["", "\n\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text | Text | Text |\n\n"],
}
});
console.log('EasyMDE initialized successfully');
// Auto-generate slug from title
var titleField = document.querySelector('input[name="titel"]');
var slugField = document.querySelector('input[name="slug"]');
if (titleField && slugField) {
titleField.addEventListener('input', function() {
// Only auto-generate if slug is empty or matches previous title
if (!slugField.value || slugField.getAttribute('data-auto-generated') === 'true') {
var title = this.value;
var slug = title.toLowerCase()
.replace(/ä/g, 'ae').replace(/ö/g, 'oe').replace(/ü/g, 'ue').replace(/ß/g, 'ss')
.replace(/[^\w\s-]/g, '')
.replace(/[-\s]+/g, '-')
.replace(/^-+|-+$/g, '');
slugField.value = slug;
slugField.setAttribute('data-auto-generated', 'true');
}
});
// Mark slug as manually edited if user changes it
slugField.addEventListener('input', function() {
slugField.setAttribute('data-auto-generated', 'false');
});
}
} }
// Auto-generate slug from title
document.getElementById('{{ form.titel.id_for_label }}').addEventListener('input', function() {
var title = this.value;
var slug = title.toLowerCase()
.replace(/ä/g, 'ae').replace(/ö/g, 'oe').replace(/ü/g, 'ue').replace(/ß/g, 'ss')
.replace(/[^\w\s-]/g, '')
.replace(/[-\s]+/g, '-')
.trim();
document.getElementById('{{ form.slug.id_for_label }}').value = slug;
});
// Update textarea on form submit
document.getElementById('geschichteForm').addEventListener('submit', function() {
document.getElementById('id_inhalt').value = quill.root.innerHTML;
});
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -8,6 +8,18 @@
<div class="col-12"> <div class="col-12">
<div class="card shadow"> <div class="card shadow">
<div class="card-body text-center py-5"> <div class="card-body text-center py-5">
<!-- Logo Placeholder -->
<div class="mb-4">
<div class="logo-placeholder mx-auto mb-3" style="width: 150px; height: 150px; border: 3px dashed #dee2e6; border-radius: 50%; display: flex; align-items: center; justify-content: center; background-color: #f8f9fa;">
<div class="text-center text-muted">
<i class="fas fa-image fa-2x mb-2"></i>
<div style="font-size: 0.8rem;">Logo hier<br>einfügen</div>
</div>
</div>
<!-- Alternative: Replace above div with actual logo when available -->
<!-- <img src="{% load static %}{% static 'images/stiftung-logo.png' %}" alt="Stiftung Logo" class="img-fluid mb-3" style="max-height: 150px;"> -->
</div>
<h1 class="display-4 mb-4"> <h1 class="display-4 mb-4">
<i class="fas fa-landmark text-primary me-3"></i>van Hees-Theyssen-Vogel'sche Stiftung <i class="fas fa-landmark text-primary me-3"></i>van Hees-Theyssen-Vogel'sche Stiftung
</h1> </h1>
@@ -40,10 +52,10 @@
<div class="col-md-4"> <div class="col-md-4">
<div class="card h-100 border-0 shadow-sm"> <div class="card h-100 border-0 shadow-sm">
<div class="card-body text-center p-4"> <div class="card-body text-center p-4">
<i class="fas fa-gift fa-3x text-success mb-3"></i> <i class="fas fa-hand-holding-usd fa-3x text-success mb-3"></i>
<h5 class="card-title">💰 Förderungsverwaltung</h5> <h5 class="card-title">💰 Unterstützungsverwaltung</h5>
<p class="card-text">Erfassen und verfolgen Sie Förderungen, Beträge und Verwendungsnachweise systematisch.</p> <p class="card-text">Erfassen und verfolgen Sie Unterstützungen, Beträge und Verwendungsnachweise systematisch.</p>
<a href="{% url 'stiftung:foerderung_list' %}" class="btn btn-outline-success btn-sm mt-2"> <a href="{% url 'stiftung:unterstuetzungen_all' %}" class="btn btn-outline-success btn-sm mt-2">
<i class="fas fa-external-link-alt me-1"></i>Öffnen <i class="fas fa-external-link-alt me-1"></i>Öffnen
</a> </a>
</div> </div>
@@ -64,6 +76,109 @@
</div> </div>
</div> </div>
<!-- Calendar and Events Section -->
<div class="row g-4 mb-5">
<div class="col-lg-8">
<div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="fas fa-calendar-alt me-2"></i>Anstehende Termine & Ereignisse
</h5>
</div>
<div class="card-body">
{% if overdue_events %}
<div class="alert alert-danger" role="alert">
<h6><i class="fas fa-exclamation-triangle me-2"></i>Überfällige Termine ({{ overdue_events|length }})</h6>
{% for event in overdue_events %}
<div class="d-flex justify-content-between align-items-center border-bottom py-2">
<div>
<i class="{{ event.icon }} me-2"></i>
<strong>{{ event.title }}</strong>
<small class="text-muted d-block">{{ event.description }}</small>
</div>
<span class="badge bg-danger">{{ event.date }}</span>
</div>
{% endfor %}
</div>
{% endif %}
{% if upcoming_events %}
<h6 class="text-muted mb-3">Nächste {{ upcoming_events|length }} Termine</h6>
{% for event in upcoming_events %}
<div class="d-flex justify-content-between align-items-center border-bottom py-2">
<div class="flex-grow-1">
<i class="{{ event.icon }} me-2 text-{{ event.color }}"></i>
<strong>{{ event.title }}</strong>
{% if event.description %}
<small class="text-muted d-block">{{ event.description }}</small>
{% endif %}
</div>
<div class="text-end">
<span class="badge bg-{{ event.color }}">{{ event.date }}</span>
{% if event.time %}
<small class="d-block text-muted">{{ event.time }}</small>
{% endif %}
</div>
</div>
{% empty %}
<div class="text-center py-4 text-muted">
<i class="fas fa-calendar-check fa-3x mb-3"></i>
<p>Keine anstehenden Termine in den nächsten 14 Tagen.</p>
</div>
{% endfor %}
<div class="text-center mt-3">
<a href="{% url 'stiftung:kalender' %}" class="btn btn-outline-primary btn-sm">
<i class="fas fa-calendar me-1"></i>Vollständigen Kalender anzeigen
</a>
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-lg-4">
<!-- Mini Calendar -->
<div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-info text-white">
<h6 class="mb-0">
<i class="fas fa-calendar me-2"></i>{{ today|date:"F Y" }}
</h6>
</div>
<div class="card-body p-2">
<div class="mini-calendar">
<!-- Calendar will be generated by JavaScript -->
<div id="mini-calendar" data-events="{{ current_month_events|length }}">
<!-- Mini calendar grid will be inserted here -->
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-success text-white">
<h6 class="mb-0">
<i class="fas fa-plus me-2"></i>Schnellzugriff
</h6>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-outline-primary btn-sm">
<i class="fas fa-calendar-plus me-1"></i>Termin hinzufügen
</a>
<a href="{% url 'stiftung:unterstuetzung_create' %}" class="btn btn-outline-success btn-sm">
<i class="fas fa-euro-sign me-1"></i>Zahlung planen
</a>
<a href="{% url 'stiftung:destinataer_create' %}" class="btn btn-outline-info btn-sm">
<i class="fas fa-user-plus me-1"></i>Destinatär anlegen
</a>
</div>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-5"> <div class="row g-4 mb-5">
<div class="col-md-4"> <div class="col-md-4">
<div class="card h-100 border-0 shadow-sm"> <div class="card h-100 border-0 shadow-sm">
@@ -97,7 +212,7 @@
<i class="fas fa-handshake fa-3x text-secondary mb-3"></i> <i class="fas fa-handshake fa-3x text-secondary mb-3"></i>
<h5 class="card-title">🤝 Verpachtungsverwaltung</h5> <h5 class="card-title">🤝 Verpachtungsverwaltung</h5>
<p class="card-text">Organisieren Sie Pachtverträge und deren Verwaltung effizient.</p> <p class="card-text">Organisieren Sie Pachtverträge und deren Verwaltung effizient.</p>
<a href="{% url 'stiftung:land_list' %}" class="btn btn-outline-secondary btn-sm mt-2"> <a href="{% url 'stiftung:verpachtung_list' %}" class="btn btn-outline-secondary btn-sm mt-2">
<i class="fas fa-external-link-alt me-1"></i>Öffnen <i class="fas fa-external-link-alt me-1"></i>Öffnen
</a> </a>
</div> </div>
@@ -176,4 +291,165 @@
</div> </div>
</div> </div>
{% endblock %}
{% block extra_css %}
<style>
.mini-calendar {
font-size: 0.8rem;
}
.mini-calendar .calendar-day {
width: 32px;
height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
margin: 1px;
border-radius: 4px;
cursor: pointer;
}
.mini-calendar .calendar-day.today {
background-color: var(--bs-primary);
color: white;
font-weight: bold;
}
.mini-calendar .calendar-day.has-events {
background-color: var(--bs-success);
color: white;
}
.mini-calendar .calendar-day:hover {
background-color: var(--bs-light);
}
.mini-calendar .calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.mini-calendar .calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
text-align: center;
}
.mini-calendar .calendar-weekday {
font-weight: bold;
color: var(--bs-secondary);
padding: 4px;
font-size: 0.7rem;
}
</style>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Generate mini calendar for current month
const today = new Date();
const currentMonth = today.getMonth();
const currentYear = today.getFullYear();
function generateMiniCalendar(year, month) {
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const startDate = new Date(firstDay);
startDate.setDate(startDate.getDate() - firstDay.getDay()); // Start from Sunday
const calendarEl = document.getElementById('mini-calendar');
let html = '<div class="calendar-grid">';
// Weekday headers
const weekdays = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
weekdays.forEach(day => {
html += `<div class="calendar-weekday">${day}</div>`;
});
// Generate calendar days
const currentDate = new Date(startDate);
for (let i = 0; i < 42; i++) { // 6 weeks
const isCurrentMonth = currentDate.getMonth() === month;
const isToday = currentDate.toDateString() === today.toDateString();
let classes = ['calendar-day'];
if (isToday) classes.push('today');
if (!isCurrentMonth) classes.push('text-muted');
html += `<div class="${classes.join(' ')}">${currentDate.getDate()}</div>`;
currentDate.setDate(currentDate.getDate() + 1);
if (currentDate > lastDay && currentDate.getDay() === 0) {
break; // Stop at end of month on Sunday
}
}
html += '</div>';
calendarEl.innerHTML = html;
}
// Generate the current month calendar
generateMiniCalendar(currentYear, currentMonth);
});
</script>
<style>
/* Mini Calendar Styles */
.mini-calendar .calendar-day {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
border-radius: 3px;
cursor: pointer;
transition: background-color 0.2s;
}
.mini-calendar .calendar-day:hover {
background-color: var(--bs-primary-bg-subtle);
}
.mini-calendar .calendar-day.today {
background-color: var(--bs-primary);
color: white;
font-weight: bold;
}
.mini-calendar .calendar-header {
font-weight: 600;
color: var(--bs-secondary);
font-size: 0.75rem;
}
/* Calendar event indicators */
.calendar-events-indicator {
position: absolute;
bottom: 2px;
right: 2px;
width: 6px;
height: 6px;
background-color: var(--bs-warning);
border-radius: 50%;
}
/* Overdue events styling */
.alert-danger .border-bottom:last-child {
border-bottom: none !important;
}
/* Calendar card hover effects */
.card.border-left-primary { border-left-color: var(--bs-primary) !important; }
.card.border-left-success { border-left-color: var(--bs-success) !important; }
.card.border-left-warning { border-left-color: var(--bs-warning) !important; }
.card.border-left-danger { border-left-color: var(--bs-danger) !important; }
.card.border-left-info { border-left-color: var(--bs-info) !important; }
</style>
{% endblock %} {% endblock %}

View File

@@ -0,0 +1,346 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Header -->
<div class="d-sm-flex align-items-center justify-content-between mb-4">
<h1 class="h3 mb-0 text-gray-800">
<i class="fas fa-cogs me-2"></i>{{ title }}
</h1>
<div class="d-flex gap-2">
<a href="{% url 'stiftung:kalender' %}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-1"></i>Zurück zum Kalender
</a>
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-success">
<i class="fas fa-plus me-1"></i>Neuer Termin
</a>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-primary shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
Benutzerdefinierte Termine
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ stats.custom_count }}</div>
</div>
<div class="col-auto">
<i class="fas fa-calendar fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-success shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">
Zahlungsereignisse
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ stats.payment_events }}</div>
</div>
<div class="col-auto">
<i class="fas fa-euro-sign fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-info shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">
Pachtverträge
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ stats.lease_events }}</div>
</div>
<div class="col-auto">
<i class="fas fa-handshake fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-warning shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
Geburtstage
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ stats.birthday_events }}</div>
</div>
<div class="col-auto">
<i class="fas fa-birthday-cake fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Filter Controls -->
<div class="row">
<div class="col-12">
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-filter me-2"></i>Ereignisquellen & Filter
</h6>
</div>
<div class="card-body">
<form method="get" class="row g-3">
<!-- Event Source Toggles -->
<div class="col-12">
<label class="form-label fw-bold">Ereignisquellen anzeigen:</label>
<div class="row">
<div class="col-md-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="show_custom"
name="show_custom" value="true"
{% if show_custom %}checked{% endif %}
onchange="this.form.submit()">
<label class="form-check-label" for="show_custom">
<i class="fas fa-calendar text-primary me-1"></i>Benutzerdefinierte Termine
</label>
</div>
</div>
<div class="col-md-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="show_payments"
name="show_payments" value="true"
{% if show_payments %}checked{% endif %}
onchange="this.form.submit()">
<label class="form-check-label" for="show_payments">
<i class="fas fa-euro-sign text-success me-1"></i>Zahlungserinnerungen
</label>
</div>
</div>
<div class="col-md-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="show_leases"
name="show_leases" value="true"
{% if show_leases %}checked{% endif %}
onchange="this.form.submit()">
<label class="form-check-label" for="show_leases">
<i class="fas fa-handshake text-info me-1"></i>Pachtverträge
</label>
</div>
</div>
<div class="col-md-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="show_birthdays"
name="show_birthdays" value="true"
{% if show_birthdays %}checked{% endif %}
onchange="this.form.submit()">
<label class="form-check-label" for="show_birthdays">
<i class="fas fa-birthday-cake text-warning me-1"></i>Geburtstage
</label>
</div>
</div>
</div>
</div>
<!-- Additional Filters -->
<div class="col-md-4">
<label for="category" class="form-label">Kategorie:</label>
<select class="form-control" id="category" name="category" onchange="this.form.submit()">
<option value="">Alle Kategorien</option>
{% for value, display in categories %}
<option value="{{ value }}" {% if category_filter == value %}selected{% endif %}>
{{ display }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label for="priority" class="form-label">Priorität:</label>
<select class="form-control" id="priority" name="priority" onchange="this.form.submit()">
<option value="">Alle Prioritäten</option>
{% for value, display in priorities %}
<option value="{{ value }}" {% if priority_filter == value %}selected{% endif %}>
{{ display }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label class="form-label">&nbsp;</label>
<div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-search me-1"></i>Filter anwenden
</button>
<a href="{% url 'stiftung:kalender_admin' %}" class="btn btn-outline-secondary">
<i class="fas fa-undo me-1"></i>Zurücksetzen
</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Events Table -->
<div class="row">
<div class="col-12">
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-list me-2"></i>Ereignisse ({{ stats.total_events }} Einträge)
</h6>
</div>
<div class="card-body">
{% if events %}
<div class="table-responsive">
<table class="table table-bordered" id="eventsTable">
<thead>
<tr>
<th>Datum</th>
<th>Titel</th>
<th>Kategorie</th>
<th>Priorität</th>
<th>Quelle</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{% for event in events %}
<tr>
<td>
<strong>{{ event.date|date:"d.m.Y" }}</strong>
{% if event.time %}
<br><small class="text-muted">{{ event.time|time:"H:i" }}</small>
{% endif %}
</td>
<td>
<i class="{{ event.icon }} me-2"></i>{{ event.title }}
{% if event.description %}
<br><small class="text-muted">{{ event.description|truncatechars:50 }}</small>
{% endif %}
</td>
<td>
<span class="badge bg-secondary">{{ event.category_display }}</span>
</td>
<td>
<span class="badge bg-{% if event.priority == 'hoch' %}danger{% elif event.priority == 'mittel' %}warning{% else %}secondary{% endif %}">
{% if event.priority == 'hoch' %}
<i class="fas fa-exclamation-triangle me-1"></i>
{% elif event.priority == 'mittel' %}
<i class="fas fa-exclamation-circle me-1"></i>
{% else %}
<i class="fas fa-info-circle me-1"></i>
{% endif %}
{{ event.priority_display|default:"Normal" }}
</span>
</td>
<td>
{% if event.source == 'custom' %}
<span class="badge bg-primary">Benutzerdefiniert</span>
{% elif event.source == 'payment' %}
<span class="badge bg-success">Zahlung</span>
{% elif event.source == 'lease' %}
<span class="badge bg-info">Pacht</span>
{% elif event.source == 'birthday' %}
<span class="badge bg-warning">Geburtstag</span>
{% else %}
<span class="badge bg-secondary">System</span>
{% endif %}
</td>
<td>
{% if event.source == 'custom' %}
{% if event.completed %}
<span class="badge bg-success">
<i class="fas fa-check me-1"></i>Erledigt
</span>
{% else %}
<span class="badge bg-warning">
<i class="fas fa-clock me-1"></i>Ausstehend
</span>
{% endif %}
{% else %}
<span class="badge bg-light text-dark">
<i class="fas fa-robot me-1"></i>Automatisch
</span>
{% endif %}
</td>
<td>
{% if event.source == 'custom' and event.id %}
<div class="btn-group btn-group-sm">
<a href="{% url 'stiftung:kalender_detail' event.id %}"
class="btn btn-outline-primary btn-sm" title="Details">
<i class="fas fa-eye"></i>
</a>
<a href="{% url 'stiftung:kalender_edit' event.id %}"
class="btn btn-outline-secondary btn-sm" title="Bearbeiten">
<i class="fas fa-edit"></i>
</a>
<a href="{% url 'stiftung:kalender_delete' event.id %}"
class="btn btn-outline-danger btn-sm" title="Löschen">
<i class="fas fa-trash"></i>
</a>
</div>
{% else %}
<span class="text-muted small">
<i class="fas fa-lock me-1"></i>Systemereignis
</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-calendar-times fa-3x text-muted mb-3"></i>
<h5 class="text-muted">Keine Ereignisse gefunden</h5>
<p class="text-muted">
Aktivieren Sie Ereignisquellen oder fügen Sie neue Termine hinzu.
</p>
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Neuen Termin hinzufügen
</a>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<style>
.border-left-primary { border-left: 0.25rem solid #4e73df !important; }
.border-left-success { border-left: 0.25rem solid #1cc88a !important; }
.border-left-info { border-left: 0.25rem solid #36b9cc !important; }
.border-left-warning { border-left: 0.25rem solid #f6c23e !important; }
.form-switch .form-check-input {
width: 2rem;
height: 1rem;
}
#eventsTable th {
background-color: #f8f9fc;
border-top: none;
}
</style>
{% endblock %}

View File

@@ -0,0 +1,337 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Calendar Header with View Controls -->
<div class="d-sm-flex align-items-center justify-content-between mb-4">
<h1 class="h3 mb-0 text-gray-800">
<i class="fas fa-list me-2"></i>{{ title }}
</h1>
<div class="d-flex gap-2">
<!-- View Type Buttons -->
<div class="btn-group" role="group">
<a href="?view=month&year={{ year }}&month={{ month }}"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-calendar me-1"></i>Monat
</a>
<a href="?view=week"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-calendar-week me-1"></i>Woche
</a>
<a href="?view=agenda"
class="btn btn-sm btn-primary">
<i class="fas fa-list me-1"></i>Agenda
</a>
<a href="?view=list"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-list-ul me-1"></i>Liste
</a>
</div>
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-success">
<i class="fas fa-plus me-1"></i>Neuer Termin
</a>
</div>
</div>
<!-- Agenda View -->
<div class="row">
<div class="col-12">
<div class="card shadow">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-0">
<i class="fas fa-clock me-2"></i>Agenda - Chronologische Übersicht
</h6>
<div class="btn-group btn-group-sm">
<!-- Date range filters -->
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
id="dateRangeDropdown" data-bs-toggle="dropdown">
Zeitraum
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="?view=agenda&range=today">Heute</a></li>
<li><a class="dropdown-item" href="?view=agenda&range=week">Diese Woche</a></li>
<li><a class="dropdown-item" href="?view=agenda&range=month">Dieser Monat</a></li>
<li><a class="dropdown-item" href="?view=agenda&range=3months">Nächste 3 Monate</a></li>
<li><a class="dropdown-item" href="?view=agenda">Alle</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="card-body p-0">
{% if events %}
<div class="agenda-timeline">
{% regroup events by date as events_by_date %}
{% for date_group in events_by_date %}
<div class="timeline-date-group">
<div class="date-divider">
<div class="date-line"></div>
<div class="date-badge {% if date_group.grouper == today %}today{% elif date_group.grouper < today %}past{% endif %}">
<div class="date-day">{{ date_group.grouper|date:"d" }}</div>
<div class="date-month-year">
{{ date_group.grouper|date:"M Y" }}
</div>
<div class="date-weekday">{{ date_group.grouper|date:"l" }}</div>
</div>
<div class="date-line"></div>
</div>
<div class="events-for-date">
{% for event in date_group.list %}
<div class="timeline-event event-{{ event.category }} {% if event.priority == 'hoch' %}high-priority{% elif event.priority == 'mittel' %}medium-priority{% endif %}">
<div class="event-time-indicator">
{% if event.time %}
<div class="event-time">{{ event.time|time:"H:i" }}</div>
{% else %}
<div class="event-time all-day">
<i class="fas fa-clock"></i>
<span>ganztags</span>
</div>
{% endif %}
</div>
<div class="event-content">
<div class="event-header">
<h6 class="event-title mb-1">
<i class="{{ event.icon }} me-2"></i>{{ event.title }}
{% if event.priority == 'hoch' %}
<span class="badge bg-danger ms-2">
<i class="fas fa-exclamation-triangle"></i>
</span>
{% endif %}
</h6>
<div class="event-meta">
<span class="badge bg-secondary me-2">{{ event.category_display }}</span>
{% if event.destinataer %}
<span class="badge bg-info">
<i class="fas fa-user me-1"></i>{{ event.destinataer }}
</span>
{% endif %}
</div>
</div>
{% if event.description %}
<div class="event-description">
{{ event.description }}
</div>
{% endif %}
{% if event.location %}
<div class="event-location">
<i class="fas fa-map-marker-alt me-1"></i>{{ event.location }}
</div>
{% endif %}
<div class="event-actions">
<a href="{% url 'stiftung:kalender_detail' event.id %}"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-eye me-1"></i>Details
</a>
<a href="{% url 'stiftung:kalender_edit' event.id %}"
class="btn btn-sm btn-outline-secondary">
<i class="fas fa-edit me-1"></i>Bearbeiten
</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-calendar-times fa-3x text-muted mb-3"></i>
<h5 class="text-muted">Keine Termine gefunden</h5>
<p class="text-muted">Fügen Sie einen neuen Termin hinzu oder erweitern Sie den Zeitraum.</p>
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Termin hinzufügen
</a>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<style>
.agenda-timeline {
padding: 20px;
}
.timeline-date-group {
margin-bottom: 40px;
}
.date-divider {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.date-line {
flex: 1;
height: 2px;
background: linear-gradient(to right, transparent, #dee2e6, transparent);
}
.date-badge {
background: white;
border: 2px solid #dee2e6;
border-radius: 15px;
padding: 15px 25px;
text-align: center;
margin: 0 20px;
min-width: 120px;
}
.date-badge.today {
background: #e3f2fd;
border-color: #2196f3;
color: #1976d2;
}
.date-badge.past {
background: #f5f5f5;
border-color: #bdbdbd;
color: #757575;
}
.date-day {
font-size: 2rem;
font-weight: bold;
line-height: 1;
}
.date-month-year {
font-size: 0.9rem;
font-weight: 600;
margin: 5px 0;
}
.date-weekday {
font-size: 0.8rem;
text-transform: uppercase;
opacity: 0.7;
}
.events-for-date {
padding-left: 40px;
}
.timeline-event {
display: flex;
margin-bottom: 20px;
border-left: 4px solid;
border-radius: 8px;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
}
.timeline-event:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
transform: translateX(5px);
transition: all 0.3s ease;
}
.timeline-event.high-priority {
box-shadow: 0 2px 8px rgba(244, 67, 54, 0.3);
}
.event-time-indicator {
background: #f8f9fa;
padding: 20px 15px;
min-width: 120px;
display: flex;
align-items: center;
justify-content: center;
border-right: 1px solid #dee2e6;
}
.event-time {
font-size: 1.2rem;
font-weight: bold;
color: #495057;
}
.event-time.all-day {
font-size: 0.9rem;
text-align: center;
color: #6c757d;
}
.event-time.all-day i {
display: block;
margin-bottom: 5px;
}
.event-content {
flex: 1;
padding: 20px;
}
.event-title {
color: #343a40;
margin-bottom: 8px;
}
.event-meta {
margin-bottom: 12px;
}
.event-description {
color: #6c757d;
margin-bottom: 12px;
line-height: 1.5;
}
.event-location {
color: #6c757d;
margin-bottom: 15px;
font-size: 0.9rem;
}
.event-actions {
display: flex;
gap: 8px;
}
/* Event category colors */
.event-termin { border-left-color: #2196f3; }
.event-zahlung { border-left-color: #ff9800; }
.event-deadline { border-left-color: #f44336; }
.event-geburtstag { border-left-color: #4caf50; }
.event-vertrag { border-left-color: #9c27b0; }
.event-pruefung { border-left-color: #009688; }
@media (max-width: 768px) {
.timeline-event {
flex-direction: column;
}
.event-time-indicator {
min-width: auto;
padding: 15px;
border-right: none;
border-bottom: 1px solid #dee2e6;
}
.events-for-date {
padding-left: 20px;
}
.date-badge {
margin: 0 10px;
min-width: 100px;
padding: 10px 15px;
}
}
</style>
{% endblock %}

View File

@@ -0,0 +1,125 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="d-sm-flex align-items-center justify-content-between mb-4">
<h1 class="h3 mb-0 text-gray-800">
<i class="fas fa-calendar-plus me-2"></i>{{ title }}
</h1>
</div>
<div class="row">
<div class="col-lg-8">
<div class="card shadow">
<div class="card-body">
<form method="post">
{% csrf_token %}
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="titel" class="form-label">Titel *</label>
<input type="text" class="form-control" id="titel" name="titel" required>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label for="datum" class="form-label">Datum *</label>
<input type="date" class="form-control" id="datum" name="datum" required>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label for="zeit" class="form-label">Uhrzeit</label>
<input type="time" class="form-control" id="zeit" name="zeit">
<small class="text-muted">Leer lassen für ganztägig</small>
</div>
</div>
</div>
<div class="mb-3">
<label for="beschreibung" class="form-label">Beschreibung</label>
<textarea class="form-control" id="beschreibung" name="beschreibung" rows="3"></textarea>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="kategorie" class="form-label">Kategorie</label>
<select class="form-select" id="kategorie" name="kategorie">
<option value="termin">Termin/Meeting</option>
<option value="zahlung">Zahlungserinnerung</option>
<option value="deadline">Frist/Deadline</option>
<option value="geburtstag">Geburtstag</option>
<option value="vertrag">Vertrag läuft aus</option>
<option value="pruefung">Prüfung/Nachweis</option>
<option value="sonstiges">Sonstiges</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="prioritaet" class="form-label">Priorität</label>
<select class="form-select" id="prioritaet" name="prioritaet">
<option value="niedrig">Niedrig</option>
<option value="normal" selected>Normal</option>
<option value="hoch">Hoch</option>
<option value="kritisch">Kritisch</option>
</select>
</div>
</div>
</div>
<div class="d-flex justify-content-between">
<a href="{% url 'stiftung:kalender' %}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-1"></i>Abbrechen
</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-1"></i>Speichern
</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card border-0 bg-light">
<div class="card-body">
<h6 class="card-title">
<i class="fas fa-info-circle me-2"></i>Hilfe
</h6>
<p class="small text-muted">
Erstellen Sie hier neue Kalenderereignisse für wichtige Termine,
Zahlungserinnerungen oder andere stiftungsbezogene Ereignisse.
</p>
<hr>
<h6 class="small">Kategorien:</h6>
<ul class="small text-muted">
<li><strong>Termin:</strong> Meetings, Besprechungen</li>
<li><strong>Zahlung:</strong> Fällige Unterstützungen</li>
<li><strong>Deadline:</strong> Fristen für Nachweise</li>
<li><strong>Vertrag:</strong> Auslaufende Pachtverträge</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<script>
// Set today as default date
document.addEventListener('DOMContentLoaded', function() {
const dateInput = document.getElementById('datum');
if (dateInput && !dateInput.value) {
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, '0');
const dd = String(today.getDate()).padStart(2, '0');
dateInput.value = `${yyyy}-${mm}-${dd}`;
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<h1><i class="fas fa-trash-alt me-2"></i>{{ title }}</h1>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="card shadow">
<div class="card-body">
<div class="alert alert-warning" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
Löschfunktion wird implementiert.
</div>
<div class="mt-4">
<a href="{% url 'stiftung:kalender' %}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-1"></i>Zurück zum Kalender
</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,108 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Header -->
<div class="d-sm-flex align-items-center justify-content-between mb-4">
<h1 class="h3 mb-0 text-gray-800 text-danger">
<i class="fas fa-exclamation-triangle me-2"></i>{{ title }}
</h1>
<div class="d-flex gap-2">
<a href="{% url 'stiftung:kalender_detail' event.pk %}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-1"></i>Zurück
</a>
</div>
</div>
<!-- Confirmation Form -->
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card shadow border-danger">
<div class="card-header bg-danger text-white">
<h5 class="mb-0">
<i class="fas fa-trash me-2"></i>Termin löschen - Bestätigung erforderlich
</h5>
</div>
<div class="card-body">
<div class="alert alert-danger" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Achtung!</strong> Diese Aktion kann nicht rückgängig gemacht werden.
</div>
<p class="mb-4">
Sind Sie sicher, dass Sie den folgenden Kalendereintrag permanent löschen möchten?
</p>
<!-- Event Details Summary -->
<div class="bg-light p-4 rounded mb-4">
<h6 class="text-muted mb-3">Zu löschender Termin:</h6>
<div class="row mb-2">
<div class="col-sm-3"><strong>Titel:</strong></div>
<div class="col-sm-9">{{ event.titel }}</div>
</div>
<div class="row mb-2">
<div class="col-sm-3"><strong>Datum:</strong></div>
<div class="col-sm-9">
{{ event.datum|date:"d.m.Y" }}
{% if event.uhrzeit %}um {{ event.uhrzeit|time:"H:i" }} Uhr{% endif %}
</div>
</div>
<div class="row mb-2">
<div class="col-sm-3"><strong>Kategorie:</strong></div>
<div class="col-sm-9">
<span class="badge bg-info">{{ event.get_kategorie_display }}</span>
</div>
</div>
{% if event.beschreibung %}
<div class="row mb-2">
<div class="col-sm-3"><strong>Beschreibung:</strong></div>
<div class="col-sm-9">{{ event.beschreibung|truncatechars:100 }}</div>
</div>
{% endif %}
<div class="row">
<div class="col-sm-3"><strong>Erstellt:</strong></div>
<div class="col-sm-9">
{{ event.erstellt_am|date:"d.m.Y H:i" }}
{% if event.erstellt_von %}von {{ event.erstellt_von }}{% endif %}
</div>
</div>
</div>
<form method="post" class="d-flex justify-content-between align-items-center">
{% csrf_token %}
<a href="{% url 'stiftung:kalender_detail' event.pk %}" class="btn btn-secondary">
<i class="fas fa-times me-1"></i>Abbrechen
</a>
<button type="submit" class="btn btn-danger">
<i class="fas fa-trash me-1"></i>Ja, endgültig löschen
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<script>
// Add confirmation dialog as extra safety measure
document.addEventListener('DOMContentLoaded', function() {
const deleteForm = document.querySelector('form');
if (deleteForm) {
deleteForm.addEventListener('submit', function(e) {
if (!confirm('Sind Sie wirklich sicher? Diese Aktion kann nicht rückgängig gemacht werden!')) {
e.preventDefault();
}
});
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,167 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Header -->
<div class="d-sm-flex align-items-center justify-content-between mb-4">
<h1 class="h3 mb-0 text-gray-800">
<i class="fas fa-calendar-alt me-2"></i>{{ title }}
</h1>
<div class="d-flex gap-2">
<a href="{% url 'stiftung:kalender' %}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-1"></i>Zurück zum Kalender
</a>
<a href="{% url 'stiftung:kalender_edit' event.pk %}" class="btn btn-primary">
<i class="fas fa-edit me-1"></i>Bearbeiten
</a>
<a href="{% url 'stiftung:kalender_delete' event.pk %}" class="btn btn-danger">
<i class="fas fa-trash me-1"></i>Löschen
</a>
</div>
</div>
<!-- Event Details -->
<div class="row">
<div class="col-lg-8">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="fas fa-info-circle me-2"></i>Termindetails
</h5>
</div>
<div class="card-body">
<div class="row mb-4">
<div class="col-md-6">
<h6 class="text-muted">Titel</h6>
<p class="h5">{{ event.titel }}</p>
</div>
<div class="col-md-6">
<h6 class="text-muted">Datum</h6>
<p class="h5">
<i class="fas fa-calendar me-2"></i>{{ event.datum|date:"d.m.Y" }}
{% if event.uhrzeit %}
<br><small class="text-muted">
<i class="fas fa-clock me-1"></i>{{ event.uhrzeit|time:"H:i" }} Uhr
</small>
{% endif %}
</p>
</div>
</div>
<div class="row mb-4">
<div class="col-md-6">
<h6 class="text-muted">Kategorie</h6>
<span class="badge bg-info fs-6">
<i class="fas fa-tag me-1"></i>{{ event.get_kategorie_display }}
</span>
</div>
<div class="col-md-6">
<h6 class="text-muted">Priorität</h6>
<span class="badge bg-{% if event.prioritaet == 'hoch' %}danger{% elif event.prioritaet == 'mittel' %}warning{% else %}secondary{% endif %} fs-6">
<i class="fas fa-{% if event.prioritaet == 'hoch' %}exclamation-triangle{% elif event.prioritaet == 'mittel' %}exclamation-circle{% else %}info-circle{% endif %} me-1"></i>{{ event.get_prioritaet_display }}
</span>
</div>
</div>
{% if event.beschreibung %}
<div class="mb-4">
<h6 class="text-muted">Beschreibung</h6>
<div class="bg-light p-3 rounded">
{{ event.beschreibung|linebreaks }}
</div>
</div>
{% endif %}
{% if event.destinataer %}
<div class="row mb-4">
<div class="col-md-12">
<h6 class="text-muted">Bezogen auf Destinatär</h6>
<p>
<i class="fas fa-user me-2"></i>
<a href="{% url 'stiftung:destinataer_detail' event.destinataer.pk %}">
{{ event.destinataer }}
</a>
</p>
</div>
</div>
{% endif %}
{% if event.verpachtung %}
<div class="row mb-4">
<div class="col-md-12">
<h6 class="text-muted">Bezogen auf Verpachtung</h6>
<p>
<i class="fas fa-handshake me-2"></i>
<a href="{% url 'stiftung:verpachtung_detail' event.verpachtung.pk %}">
{{ event.verpachtung }}
</a>
</p>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-lg-4">
<!-- Status Card -->
<div class="card shadow mb-4">
<div class="card-header bg-{% if event.erledigt %}success{% else %}warning{% endif %} text-white">
<h6 class="mb-0">
<i class="fas fa-{% if event.erledigt %}check-circle{% else %}clock{% endif %} me-2"></i>Status
</h6>
</div>
<div class="card-body">
{% if event.erledigt %}
<div class="text-center">
<i class="fas fa-check-circle fa-3x text-success mb-3"></i>
<h6 class="text-success">Erledigt</h6>
<p class="text-muted">Dieser Termin wurde als erledigt markiert.</p>
</div>
{% else %}
<div class="text-center">
<i class="fas fa-clock fa-3x text-warning mb-3"></i>
<h6 class="text-warning">Ausstehend</h6>
<p class="text-muted">Dieser Termin steht noch aus.</p>
</div>
{% endif %}
</div>
</div>
<!-- Meta Information -->
<div class="card shadow">
<div class="card-header bg-secondary text-white">
<h6 class="mb-0">
<i class="fas fa-info me-2"></i>Meta-Informationen
</h6>
</div>
<div class="card-body">
<div class="mb-3">
<small class="text-muted">Erstellt von:</small><br>
<strong>{{ event.erstellt_von|default:"System" }}</strong>
</div>
<div class="mb-3">
<small class="text-muted">Erstellt am:</small><br>
<strong>{{ event.erstellt_am|date:"d.m.Y H:i" }}</strong>
</div>
{% if event.aktualisiert_am %}
<div class="mb-3">
<small class="text-muted">Zuletzt aktualisiert:</small><br>
<strong>{{ event.aktualisiert_am|date:"d.m.Y H:i" }}</strong>
</div>
{% endif %}
<div>
<small class="text-muted">Termin-ID:</small><br>
<code class="small">{{ event.pk }}</code>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<h1><i class="fas fa-calendar-alt me-2"></i>{{ title }}</h1>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="card shadow">
<div class="card-body">
<div class="alert alert-info" role="alert">
<i class="fas fa-info-circle me-2"></i>
Termin-Details werden hier angezeigt, sobald die Funktionalität vollständig implementiert ist.
</div>
<div class="mt-4">
<a href="{% url 'stiftung:kalender' %}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-1"></i>Zurück zum Kalender
</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,154 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Header -->
<div class="d-sm-flex align-items-center justify-content-between mb-4">
<h1 class="h3 mb-0 text-gray-800">
<i class="fas fa-edit me-2"></i>{{ title }}
</h1>
<div class="d-flex gap-2">
<a href="{% url 'stiftung:kalender_detail' event.pk %}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-1"></i>Zurück
</a>
</div>
</div>
<!-- Edit Form -->
<div class="row">
<div class="col-lg-8">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="fas fa-calendar-edit me-2"></i>Termin bearbeiten
</h5>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
<div class="row mb-3">
<div class="col-md-12">
<label for="titel" class="form-label">
<i class="fas fa-heading me-1"></i>Titel *
</label>
<input type="text" class="form-control" id="titel" name="titel"
value="{{ event.titel }}" required>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="datum" class="form-label">
<i class="fas fa-calendar me-1"></i>Datum *
</label>
<input type="date" class="form-control" id="datum" name="datum"
value="{{ event.datum|date:'Y-m-d' }}" required>
</div>
<div class="col-md-6">
<label for="zeit" class="form-label">
<i class="fas fa-clock me-1"></i>Uhrzeit
</label>
<input type="time" class="form-control" id="zeit" name="zeit"
value="{% if event.uhrzeit %}{{ event.uhrzeit|time:'H:i' }}{% endif %}">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="kategorie" class="form-label">
<i class="fas fa-tag me-1"></i>Kategorie
</label>
<select class="form-control" id="kategorie" name="kategorie">
<option value="termin" {% if event.kategorie == 'termin' %}selected{% endif %}>Termin</option>
<option value="zahlung" {% if event.kategorie == 'zahlung' %}selected{% endif %}>Zahlung</option>
<option value="deadline" {% if event.kategorie == 'deadline' %}selected{% endif %}>Deadline</option>
<option value="geburtstag" {% if event.kategorie == 'geburtstag' %}selected{% endif %}>Geburtstag</option>
<option value="vertrag" {% if event.kategorie == 'vertrag' %}selected{% endif %}>Vertrag</option>
<option value="pruefung" {% if event.kategorie == 'pruefung' %}selected{% endif %}>Prüfung</option>
</select>
</div>
<div class="col-md-6">
<label for="prioritaet" class="form-label">
<i class="fas fa-exclamation-circle me-1"></i>Priorität
</label>
<select class="form-control" id="prioritaet" name="prioritaet">
<option value="niedrig" {% if event.prioritaet == 'niedrig' %}selected{% endif %}>Niedrig</option>
<option value="normal" {% if event.prioritaet == 'normal' %}selected{% endif %}>Normal</option>
<option value="hoch" {% if event.prioritaet == 'hoch' %}selected{% endif %}>Hoch</option>
</select>
</div>
</div>
<div class="mb-3">
<label for="beschreibung" class="form-label">
<i class="fas fa-align-left me-1"></i>Beschreibung
</label>
<textarea class="form-control" id="beschreibung" name="beschreibung"
rows="4" placeholder="Optionale Beschreibung...">{{ event.beschreibung }}</textarea>
</div>
<div class="mb-3">
<label for="ort" class="form-label">
<i class="fas fa-map-marker-alt me-1"></i>Ort
</label>
<input type="text" class="form-control" id="ort" name="ort"
value="{{ event.ort }}" placeholder="Optional...">
</div>
<div class="mb-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="erledigt"
name="erledigt" {% if event.erledigt %}checked{% endif %}>
<label class="form-check-label" for="erledigt">
<i class="fas fa-check-circle me-1"></i>Als erledigt markieren
</label>
</div>
</div>
<div class="d-flex justify-content-between">
<a href="{% url 'stiftung:kalender_detail' event.pk %}" class="btn btn-secondary">
<i class="fas fa-times me-1"></i>Abbrechen
</a>
<button type="submit" class="btn btn-success">
<i class="fas fa-save me-1"></i>Änderungen speichern
</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-4">
<!-- Help Card -->
<div class="card shadow">
<div class="card-header bg-info text-white">
<h6 class="mb-0">
<i class="fas fa-question-circle me-2"></i>Hilfe
</h6>
</div>
<div class="card-body">
<h6>Kategorien:</h6>
<ul class="small">
<li><strong>Termin:</strong> Allgemeine Termine und Meetings</li>
<li><strong>Zahlung:</strong> Zahlungserinnerungen</li>
<li><strong>Deadline:</strong> Wichtige Fristen</li>
<li><strong>Geburtstag:</strong> Geburtstage</li>
<li><strong>Vertrag:</strong> Vertragsereignisse</li>
<li><strong>Prüfung:</strong> Überprüfungen und Kontrollen</li>
</ul>
<h6 class="mt-3">Prioritäten:</h6>
<ul class="small">
<li><span class="badge bg-secondary me-1">Niedrig</span> Optionale Termine</li>
<li><span class="badge bg-warning me-1">Normal</span> Standard-Priorität</li>
<li><span class="badge bg-danger me-1">Hoch</span> Wichtige/dringende Termine</li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,42 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<h1><i class="fas fa-calendar-plus me-2"></i>{{ title }}</h1>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="card shadow">
<div class="card-body">
<div class="alert alert-info" role="alert">
<i class="fas fa-info-circle me-2"></i>
Die Kalender-Funktionalität wird derzeit implementiert.
Bald können Sie hier Termine, Zahlungserinnerungen und wichtige Fristen verwalten.
</div>
<p>Geplante Funktionen:</p>
<ul>
<li>📅 Termine und Meetings</li>
<li>💰 Zahlungserinnerungen</li>
<li>⏰ Fristen und Deadlines</li>
<li>🎂 Geburtstage</li>
<li>📄 Vertragsauslauf-Erinnerungen</li>
</ul>
<div class="mt-4">
<a href="{% url 'stiftung:kalender' %}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-1"></i>Zurück zum Kalender
</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,60 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1><i class="fas fa-calendar-alt me-2"></i>{{ title }}</h1>
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Neuer Eintrag
</a>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">{{ current_month }}</h5>
</div>
<div class="card-body">
{% if events %}
<div class="list-group">
{% for event in events %}
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<div>
<i class="{{ event.icon }} me-2 text-{{ event.color }}"></i>
<strong>{{ event.title }}</strong>
{% if event.description %}
<p class="mb-1 text-muted">{{ event.description }}</p>
{% endif %}
</div>
<small class="text-{{ event.color }}">
{{ event.date }}
{% if event.time %} {{ event.time }}{% endif %}
</small>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-calendar-times fa-3x text-muted mb-3"></i>
<h5 class="text-muted">Keine Termine im aktuellen Monat</h5>
<p class="text-muted">Erstellen Sie Ihren ersten Kalendereintrag.</p>
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Termin hinzufügen
</a>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,140 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Calendar Header with View Controls -->
<div class="d-sm-flex align-items-center justify-content-between mb-4">
<h1 class="h3 mb-0 text-gray-800">
<i class="fas fa-list me-2"></i>{{ title }}
</h1>
<div class="d-flex gap-2">
<!-- View Type Buttons -->
<div class="btn-group" role="group">
<a href="?view=month&year={{ year }}&month={{ month }}"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-calendar me-1"></i>Monat
</a>
<a href="?view=week"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-calendar-week me-1"></i>Woche
</a>
<a href="?view=agenda"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-list me-1"></i>Agenda
</a>
<a href="?view=list"
class="btn btn-sm btn-primary">
<i class="fas fa-list-ul me-1"></i>Liste
</a>
</div>
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-success">
<i class="fas fa-plus me-1"></i>Neuer Termin
</a>
</div>
</div>
<!-- Event List -->
<div class="row">
<div class="col-12">
<div class="card shadow">
<div class="card-body">
{% if events %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th width="100">Datum</th>
<th width="60">Zeit</th>
<th>Titel</th>
<th>Beschreibung</th>
<th width="120">Kategorie</th>
<th width="100">Priorität</th>
<th width="80">Status</th>
<th width="100">Aktionen</th>
</tr>
</thead>
<tbody>
{% for event in events %}
<tr class="{% if event.overdue %}table-danger{% elif event.completed %}table-success{% endif %}">
<td>
<strong>{{ event.date|date:"d.m.Y" }}</strong>
<br>
<small class="text-muted">{{ event.date|date:"l" }}</small>
</td>
<td>
{% if event.time %}
{{ event.time }}
{% else %}
<small class="text-muted">ganztags</small>
{% endif %}
</td>
<td>
<div class="d-flex align-items-center">
<i class="{{ event.icon }} me-2 text-{{ event.color }}"></i>
<strong>{{ event.title }}</strong>
</div>
</td>
<td>
<small>{{ event.description|default:"-" }}</small>
</td>
<td>
<span class="badge bg-{{ event.color }}">
{{ event.category|title }}
</span>
</td>
<td>
{% if event.priority == 'kritisch' %}
<span class="badge bg-danger">{{ event.priority|title }}</span>
{% elif event.priority == 'hoch' %}
<span class="badge bg-warning">{{ event.priority|title }}</span>
{% elif event.priority == 'normal' %}
<span class="badge bg-primary">{{ event.priority|title }}</span>
{% else %}
<span class="badge bg-secondary">{{ event.priority|title }}</span>
{% endif %}
</td>
<td>
{% if event.overdue %}
<span class="badge bg-danger">Überfällig</span>
{% elif event.completed %}
<span class="badge bg-success">Erledigt</span>
{% else %}
<span class="badge bg-info">Offen</span>
{% endif %}
</td>
<td>
<div class="btn-group btn-group-sm">
{% if event.url %}
<a href="{{ event.url }}" class="btn btn-outline-primary" title="Details">
<i class="fas fa-eye"></i>
</a>
{% endif %}
<a href="{% url 'stiftung:kalender_edit' event.id %}" class="btn btn-outline-secondary" title="Bearbeiten">
<i class="fas fa-edit"></i>
</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-calendar-times fa-3x text-muted mb-3"></i>
<h5 class="text-muted">Keine Termine im gewählten Zeitraum</h5>
<p class="text-muted">Fügen Sie Ihren ersten Kalendereintrag hinzu.</p>
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Termin hinzufügen
</a>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,200 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Calendar Header with View Controls -->
<div class="d-sm-flex align-items-center justify-content-between mb-4">
<h1 class="h3 mb-0 text-gray-800">
<i class="fas fa-calendar-alt me-2"></i>{{ title }}
</h1>
<div class="d-flex gap-2">
<!-- View Type Buttons -->
<div class="btn-group" role="group">
<a href="?view=month&year={{ year }}&month={{ month }}"
class="btn btn-sm btn-primary">
<i class="fas fa-calendar me-1"></i>Monat
</a>
<a href="?view=week"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-calendar-week me-1"></i>Woche
</a>
<a href="?view=agenda"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-list me-1"></i>Agenda
</a>
<a href="?view=list"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-list-ul me-1"></i>Liste
</a>
</div>
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-success">
<i class="fas fa-plus me-1"></i>Neuer Termin
</a>
</div>
</div>
<!-- Navigation for Month View -->
<div class="row mb-3">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group" role="group">
<a href="?view=month&year={{ prev_year }}&month={{ prev_month }}"
class="btn btn-outline-secondary">
<i class="fas fa-chevron-left me-1"></i>Vorheriger Monat
</a>
<a href="?view=month&year={{ today.year }}&month={{ today.month }}"
class="btn btn-secondary">Heute</a>
<a href="?view=month&year={{ next_year }}&month={{ next_month }}"
class="btn btn-outline-secondary">
Nächster Monat<i class="fas fa-chevron-right ms-1"></i>
</a>
</div>
<h4 class="mb-0">{{ month_name }} {{ year }}</h4>
</div>
</div>
</div>
<!-- Month Calendar Grid -->
<div class="row">
<div class="col-12">
<div class="card shadow">
<div class="card-body p-0">
<div class="calendar-grid">
<!-- Weekday Headers -->
<div class="calendar-header">
{% for weekday in weekdays %}
<div class="weekday-header">{{ weekday }}</div>
{% endfor %}
</div>
<!-- Calendar Days -->
{% for week in calendar_grid %}
<div class="calendar-week">
{% for day_data in week %}
<div class="calendar-day {% if day_data.is_today %}today{% endif %} {% if not day_data %}empty{% endif %}">
{% if day_data %}
<div class="day-number">{{ day_data.day }}</div>
{% if day_data.events %}
<div class="day-events">
{% for event in day_data.events %}
<div class="event-item event-{{ event.category }}" title="{{ event.title }} - {{ event.description }}">
<i class="{{ event.icon }}"></i>
<span>{{ event.title|truncatechars:20 }}</span>
</div>
{% endfor %}
{% if day_data.event_count > 3 %}
<div class="more-events">+{{ day_data.event_count|add:"-3" }} weitere</div>
{% endif %}
</div>
{% endif %}
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.calendar-grid {
display: grid;
grid-template-rows: auto repeat(6, 1fr);
gap: 1px;
background-color: #dee2e6;
border: 1px solid #dee2e6;
}
.calendar-header {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
}
.weekday-header {
background-color: #f8f9fa;
padding: 10px;
text-align: center;
font-weight: bold;
color: #495057;
}
.calendar-week {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
}
.calendar-day {
background-color: white;
min-height: 120px;
padding: 8px;
position: relative;
cursor: pointer;
}
.calendar-day.empty {
background-color: #f8f9fa;
}
.calendar-day.today {
background-color: #e3f2fd;
border: 2px solid #2196f3;
}
.calendar-day:hover {
background-color: #f5f5f5;
}
.day-number {
font-weight: bold;
margin-bottom: 5px;
color: #495057;
}
.calendar-day.today .day-number {
color: #1976d2;
}
.day-events {
display: flex;
flex-direction: column;
gap: 2px;
}
.event-item {
font-size: 0.75rem;
padding: 2px 4px;
border-radius: 3px;
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
}
.event-item i {
font-size: 0.6rem;
}
.event-termin { background-color: #e3f2fd; color: #1565c0; }
.event-zahlung { background-color: #fff3e0; color: #ef6c00; }
.event-deadline { background-color: #ffebee; color: #c62828; }
.event-geburtstag { background-color: #e8f5e8; color: #2e7d32; }
.event-vertrag { background-color: #f3e5f5; color: #7b1fa2; }
.event-pruefung { background-color: #e0f2f1; color: #00695c; }
.more-events {
font-size: 0.6rem;
color: #757575;
font-style: italic;
text-align: center;
margin-top: 2px;
}
</style>
{% endblock %}

View File

@@ -0,0 +1,184 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Calendar Header with View Controls -->
<div class="d-sm-flex align-items-center justify-content-between mb-4">
<h1 class="h3 mb-0 text-gray-800">
<i class="fas fa-calendar-week me-2"></i>{{ title }}
</h1>
<div class="d-flex gap-2">
<!-- View Type Buttons -->
<div class="btn-group" role="group">
<a href="?view=month&year={{ year }}&month={{ month }}"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-calendar me-1"></i>Monat
</a>
<a href="?view=week"
class="btn btn-sm btn-primary">
<i class="fas fa-calendar-week me-1"></i>Woche
</a>
<a href="?view=agenda"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-list me-1"></i>Agenda
</a>
<a href="?view=list"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-list-ul me-1"></i>Liste
</a>
</div>
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-success">
<i class="fas fa-plus me-1"></i>Neuer Termin
</a>
</div>
</div>
<!-- Week View -->
<div class="row">
<div class="col-12">
<div class="card shadow">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-0">{{ start_date|date:"d.m.Y" }} - {{ end_date|date:"d.m.Y" }}</h6>
<div class="btn-group btn-group-sm">
<a href="?view=week&date={{ start_date|add:'-7'|date:'Y-m-d' }}" class="btn btn-outline-secondary">
<i class="fas fa-chevron-left"></i> Vorherige Woche
</a>
<a href="?view=week" class="btn btn-secondary">Diese Woche</a>
<a href="?view=week&date={{ start_date|add:'7'|date:'Y-m-d' }}" class="btn btn-outline-secondary">
Nächste Woche <i class="fas fa-chevron-right"></i>
</a>
</div>
</div>
</div>
<div class="card-body">
{% if events %}
<div class="week-timeline">
{% for i in "1234567"|make_list %}
{% with day_date=start_date|add:forloop.counter0 %}
<div class="day-column">
<div class="day-header {% if day_date == today %}today{% endif %}">
<div class="day-name">{{ day_date|date:"l" }}</div>
<div class="day-date">{{ day_date|date:"d.m" }}</div>
</div>
<div class="day-events">
{% for event in events %}
{% if event.date == day_date %}
<div class="event-block event-{{ event.category }}">
<div class="event-time">
{% if event.time %}{{ event.time }}{% else %}ganztags{% endif %}
</div>
<div class="event-title">
<i class="{{ event.icon }} me-1"></i>{{ event.title }}
</div>
{% if event.description %}
<div class="event-desc">{{ event.description|truncatechars:50 }}</div>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endwith %}
{% endfor %}
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-calendar-times fa-3x text-muted mb-3"></i>
<h5 class="text-muted">Keine Termine diese Woche</h5>
<p class="text-muted">Fügen Sie einen neuen Termin hinzu.</p>
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Termin hinzufügen
</a>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<style>
.week-timeline {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
background-color: #dee2e6;
border: 1px solid #dee2e6;
}
.day-column {
background-color: white;
min-height: 400px;
}
.day-header {
padding: 10px;
background-color: #f8f9fa;
text-align: center;
border-bottom: 1px solid #dee2e6;
}
.day-header.today {
background-color: #e3f2fd;
color: #1976d2;
font-weight: bold;
}
.day-name {
font-size: 0.8rem;
text-transform: uppercase;
margin-bottom: 2px;
}
.day-date {
font-size: 1.2rem;
font-weight: bold;
}
.day-events {
padding: 8px;
display: flex;
flex-direction: column;
gap: 4px;
}
.event-block {
padding: 8px;
border-radius: 4px;
border-left: 4px solid;
cursor: pointer;
}
.event-block:hover {
opacity: 0.8;
}
.event-time {
font-size: 0.75rem;
font-weight: bold;
margin-bottom: 2px;
}
.event-title {
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 2px;
}
.event-desc {
font-size: 0.75rem;
color: #666;
}
.event-termin { background-color: #e3f2fd; border-left-color: #2196f3; }
.event-zahlung { background-color: #fff3e0; border-left-color: #ff9800; }
.event-deadline { background-color: #ffebee; border-left-color: #f44336; }
.event-geburtstag { background-color: #e8f5e8; border-left-color: #4caf50; }
.event-vertrag { background-color: #f3e5f5; border-left-color: #9c27b0; }
.event-pruefung { background-color: #e0f2f1; border-left-color: #009688; }
</style>
{% endblock %}

38
menu-structure.csv Normal file
View File

@@ -0,0 +1,38 @@
Menu Level,Item Name,URL Name,Icon,Description,Current Order
1,Home,stiftung:home,fas fa-home,Main landing page,1
1,Dashboard,stiftung:dashboard,fas fa-tachometer-alt,Statistics and overview dashboard,2
2,Menschen & Finanzen (Dropdown),personenDropdown,fas fa-users,People and financial management,3
3,Destinatäre Header,N/A,N/A,Section header,3.1
3,Alle Destinatäre,stiftung:destinataer_list,fas fa-list,List all beneficiaries,3.2
3,Neuer Destinatär,stiftung:destinataer_create,fas fa-plus,Create new beneficiary,3.3
3,Förderungen Header,N/A,N/A,Section header,3.4
3,Alle Förderungen,stiftung:foerderung_list,fas fa-gift,List all grants,3.5
3,Neue Förderung,stiftung:foerderung_create,fas fa-plus,Create new grant,3.6
3,Unterstützungen Header,N/A,N/A,Section header,3.7
3,Alle Unterstützungen,stiftung:unterstuetzungen_all,fas fa-hand-holding-usd,List all support payments,3.8
3,Neue Unterstützung,stiftung:unterstuetzung_create,fas fa-plus,Create new support,3.9
3,Pächter Header,N/A,N/A,Section header,3.10
3,Alle Pächter,stiftung:paechter_list,fas fa-user-tie,List all tenants,3.11
2,Immobilien & Land (Dropdown),immobilienDropdown,fas fa-tree,Real estate and land management,4
3,Ländereien Header,N/A,N/A,Section header,4.1
3,Alle Ländereien,stiftung:land_list,fas fa-list,List all land parcels,4.2
3,Neue Länderei,stiftung:land_create,fas fa-plus,Create new land parcel,4.3
3,Verpachtungen Header,N/A,N/A,Section header,4.4
3,Alle Verpachtungen,stiftung:verpachtung_list,fas fa-handshake,List all leases,4.5
3,Neue Verpachtung,stiftung:verpachtung_create,fas fa-plus,Create new lease,4.6
3,Abrechnungen Header,N/A,N/A,Section header,4.7
3,Alle Abrechnungen,stiftung:land_abrechnung_list,fas fa-calculator,List all settlements,4.8
3,Neue Abrechnung,stiftung:land_abrechnung_create,fas fa-plus,Create new settlement,4.9
2,Verwaltung (Dropdown),verwaltungDropdown,fas fa-briefcase,Administration and documents,5
3,Dokumente,stiftung:dokument_management,fas fa-folder-open,Document management,5.1
3,Geschäftsführung,stiftung:geschaeftsfuehrung,fas fa-briefcase,Business management,5.2
1,Geschichte,stiftung:geschichte_list,fas fa-book-open,Foundation history wiki,6
1,Administration,stiftung:administration,fas fa-cogs,System administration,7
2,User Menu (Dropdown),userDropdown,fas fa-user,User account management,8
3,User Profile Header,N/A,N/A,Dynamic user name header,8.1
3,Mein Profil,stiftung:user_detail,fas fa-user,View user profile,8.2
3,2FA verwalten,stiftung:two_factor_setup,fas fa-shield-alt,Manage two-factor authentication,8.3
3,Benutzerverwaltung,stiftung:user_management,fas fa-users,Manage users (admin only),8.4
3,Administration,stiftung:administration,fas fa-cogs,System admin (admin only),8.5
3,Abmelden,stiftung:logout,fas fa-sign-out-alt,Logout,8.6
1,Anmelden,stiftung:login,fas fa-sign-in-alt,Login (unauthenticated users only),9
1 Menu Level Item Name URL Name Icon Description Current Order
2 1 Home stiftung:home fas fa-home Main landing page 1
3 1 Dashboard stiftung:dashboard fas fa-tachometer-alt Statistics and overview dashboard 2
4 2 Menschen & Finanzen (Dropdown) personenDropdown fas fa-users People and financial management 3
5 3 Destinatäre Header N/A N/A Section header 3.1
6 3 Alle Destinatäre stiftung:destinataer_list fas fa-list List all beneficiaries 3.2
7 3 Neuer Destinatär stiftung:destinataer_create fas fa-plus Create new beneficiary 3.3
8 3 Förderungen Header N/A N/A Section header 3.4
9 3 Alle Förderungen stiftung:foerderung_list fas fa-gift List all grants 3.5
10 3 Neue Förderung stiftung:foerderung_create fas fa-plus Create new grant 3.6
11 3 Unterstützungen Header N/A N/A Section header 3.7
12 3 Alle Unterstützungen stiftung:unterstuetzungen_all fas fa-hand-holding-usd List all support payments 3.8
13 3 Neue Unterstützung stiftung:unterstuetzung_create fas fa-plus Create new support 3.9
14 3 Pächter Header N/A N/A Section header 3.10
15 3 Alle Pächter stiftung:paechter_list fas fa-user-tie List all tenants 3.11
16 2 Immobilien & Land (Dropdown) immobilienDropdown fas fa-tree Real estate and land management 4
17 3 Ländereien Header N/A N/A Section header 4.1
18 3 Alle Ländereien stiftung:land_list fas fa-list List all land parcels 4.2
19 3 Neue Länderei stiftung:land_create fas fa-plus Create new land parcel 4.3
20 3 Verpachtungen Header N/A N/A Section header 4.4
21 3 Alle Verpachtungen stiftung:verpachtung_list fas fa-handshake List all leases 4.5
22 3 Neue Verpachtung stiftung:verpachtung_create fas fa-plus Create new lease 4.6
23 3 Abrechnungen Header N/A N/A Section header 4.7
24 3 Alle Abrechnungen stiftung:land_abrechnung_list fas fa-calculator List all settlements 4.8
25 3 Neue Abrechnung stiftung:land_abrechnung_create fas fa-plus Create new settlement 4.9
26 2 Verwaltung (Dropdown) verwaltungDropdown fas fa-briefcase Administration and documents 5
27 3 Dokumente stiftung:dokument_management fas fa-folder-open Document management 5.1
28 3 Geschäftsführung stiftung:geschaeftsfuehrung fas fa-briefcase Business management 5.2
29 1 Geschichte stiftung:geschichte_list fas fa-book-open Foundation history wiki 6
30 1 Administration stiftung:administration fas fa-cogs System administration 7
31 2 User Menu (Dropdown) userDropdown fas fa-user User account management 8
32 3 User Profile Header N/A N/A Dynamic user name header 8.1
33 3 Mein Profil stiftung:user_detail fas fa-user View user profile 8.2
34 3 2FA verwalten stiftung:two_factor_setup fas fa-shield-alt Manage two-factor authentication 8.3
35 3 Benutzerverwaltung stiftung:user_management fas fa-users Manage users (admin only) 8.4
36 3 Administration stiftung:administration fas fa-cogs System admin (admin only) 8.5
37 3 Abmelden stiftung:logout fas fa-sign-out-alt Logout 8.6
38 1 Anmelden stiftung:login fas fa-sign-in-alt Login (unauthenticated users only) 9