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