- 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
169 lines
7.0 KiB
Python
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}"
|
|
) |