- 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
166 lines
7.9 KiB
Python
166 lines
7.9 KiB
Python
"""
|
||
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() |