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

@@ -2864,7 +2864,11 @@ class GeschichteSeite(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
titel = models.CharField(max_length=200, verbose_name="Titel")
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
erstellt_am = models.DateTimeField(auto_now_add=True, verbose_name="Erstellt am")
@@ -2940,3 +2944,124 @@ class GeschichteBild(models.Model):
def __str__(self):
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))