Files
stiftung-management-system/app/stiftung/management/commands/fix_account_balances.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

169 lines
7.0 KiB
Python

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