Files
stiftung-management-system/app/stiftung/signals.py
Jan Remmer Siebels c289cc3c58 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
2025-10-05 00:38:18 +02:00

166 lines
7.9 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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()