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:
145
app/stiftung/management/commands/reconcile_balances.py
Normal file
145
app/stiftung/management/commands/reconcile_balances.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""
|
||||
Management command to reconcile account balances with actual transactions.
|
||||
|
||||
This command will:
|
||||
1. Calculate the correct balance based on all bank transactions
|
||||
2. Update the account balance to match the calculated balance
|
||||
3. Show discrepancies between stored and calculated balances
|
||||
|
||||
Usage:
|
||||
python manage.py reconcile_balances
|
||||
"""
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
from decimal import Decimal
|
||||
|
||||
from stiftung.models import StiftungsKonto, BankTransaction
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Reconcile account balances with bank transactions'
|
||||
|
||||
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 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('🔍 Reconciling account balances with transactions...')
|
||||
)
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(
|
||||
self.style.WARNING('DRY RUN MODE - No changes will be made')
|
||||
)
|
||||
|
||||
# Get accounts to process
|
||||
accounts_query = StiftungsKonto.objects.filter(aktiv=True)
|
||||
if account_filter:
|
||||
accounts_query = accounts_query.filter(kontoname__icontains=account_filter)
|
||||
|
||||
accounts = accounts_query.all()
|
||||
|
||||
if not accounts:
|
||||
self.stdout.write(
|
||||
self.style.ERROR('No accounts found matching criteria')
|
||||
)
|
||||
return
|
||||
|
||||
self.stdout.write(f"Processing {accounts.count()} account(s)...")
|
||||
|
||||
total_discrepancies = 0
|
||||
fixed_accounts = 0
|
||||
|
||||
for account in accounts:
|
||||
self.stdout.write(f"\n🏦 {account.bank_name} - {account.kontoname}")
|
||||
self.stdout.write(f" Stored Balance: €{account.saldo}")
|
||||
self.stdout.write(f" Last Updated: {account.saldo_datum}")
|
||||
|
||||
# Calculate balance from transactions
|
||||
transactions = BankTransaction.objects.filter(konto=account).order_by('datum')
|
||||
calculated_balance = Decimal('0.00')
|
||||
transaction_count = transactions.count()
|
||||
|
||||
for transaction in transactions:
|
||||
calculated_balance += transaction.betrag
|
||||
|
||||
self.stdout.write(f" Transactions: {transaction_count}")
|
||||
self.stdout.write(f" Calculated Balance: €{calculated_balance}")
|
||||
|
||||
discrepancy = account.saldo - calculated_balance
|
||||
if discrepancy != 0:
|
||||
total_discrepancies += 1
|
||||
self.stdout.write(
|
||||
self.style.WARNING(f" ⚠️ Discrepancy: €{discrepancy}")
|
||||
)
|
||||
|
||||
if not dry_run:
|
||||
# Update the account balance
|
||||
old_balance = account.saldo
|
||||
account.saldo = calculated_balance
|
||||
|
||||
# Update the balance date to the latest transaction date or today
|
||||
if transactions.exists():
|
||||
latest_transaction = transactions.order_by('-datum').first()
|
||||
account.saldo_datum = latest_transaction.datum
|
||||
else:
|
||||
account.saldo_datum = timezone.now().date()
|
||||
|
||||
account.save()
|
||||
fixed_accounts += 1
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f" ✅ Updated: €{old_balance} → €{calculated_balance}"
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
f" 📝 Would update: €{account.saldo} → €{calculated_balance}"
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(" ✅ Balance is correct")
|
||||
)
|
||||
|
||||
# Show recent transactions
|
||||
if transaction_count > 0:
|
||||
self.stdout.write(" Recent transactions:")
|
||||
recent = transactions.order_by('-datum')[:3]
|
||||
for trans in recent:
|
||||
self.stdout.write(
|
||||
f" {trans.datum}: €{trans.betrag} - {trans.verwendungszweck[:50]}"
|
||||
)
|
||||
|
||||
# Summary
|
||||
self.stdout.write("\n" + "=" * 50)
|
||||
if dry_run:
|
||||
self.stdout.write(
|
||||
f"📊 Found {total_discrepancies} account(s) with balance discrepancies"
|
||||
)
|
||||
if total_discrepancies > 0:
|
||||
self.stdout.write(" Run without --dry-run to fix these discrepancies")
|
||||
else:
|
||||
if fixed_accounts > 0:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"🎉 Fixed {fixed_accounts} account balance(s)"
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS("✅ All account balances were already correct")
|
||||
)
|
||||
Reference in New Issue
Block a user