- 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
145 lines
5.3 KiB
Python
145 lines
5.3 KiB
Python
"""
|
|
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")
|
|
) |