from django.utils.translation import gettext as _ from django.shortcuts import render, get_object_or_404, redirect from django.contrib.auth.decorators import login_required from django.contrib import messages from .models import Account, JournalEntry, JournalItem from .forms import AccountForm, JournalEntryForm from django.db.models import Sum, Q, Value, DecimalField from django.db.models.functions import Coalesce from django.utils import timezone from datetime import datetime from django.db import transaction import json @login_required def accounting_dashboard(request): total_assets = sum(acc.balance for acc in Account.objects.filter(account_type='asset')) total_liabilities = sum(acc.balance for acc in Account.objects.filter(account_type='liability')) total_equity = sum(acc.balance for acc in Account.objects.filter(account_type='equity')) # Revenue and Expenses for current month month_start = timezone.now().replace(day=1, hour=0, minute=0, second=0, microsecond=0) revenue_items = JournalItem.objects.filter( account__account_type='income', entry__date__gte=month_start ) monthly_revenue = (revenue_items.filter(type='credit').aggregate(total=Sum('amount'))['total'] or 0) - \ (revenue_items.filter(type='debit').aggregate(total=Sum('amount'))['total'] or 0) expense_items = JournalItem.objects.filter( account__account_type='expense', entry__date__gte=month_start ) monthly_expense = (expense_items.filter(type='debit').aggregate(total=Sum('amount'))['total'] or 0) - \ (expense_items.filter(type='credit').aggregate(total=Sum('amount'))['total'] or 0) context = { 'total_assets': total_assets, 'total_liabilities': total_liabilities, 'total_equity': total_equity, 'monthly_revenue': monthly_revenue, 'monthly_expense': monthly_expense, 'net_profit': monthly_revenue - monthly_expense, 'recent_entries': JournalEntry.objects.order_by('-date', '-id')[:10] } return render(request, 'accounting/dashboard.html', context) @login_required def chart_of_accounts(request): accounts = Account.objects.all().order_by('code') return render(request, 'accounting/chart_of_accounts.html', {'accounts': accounts}) @login_required def account_create_update(request, pk=None): if pk: account = get_object_or_404(Account, pk=pk) else: account = None if request.method == 'POST': form = AccountForm(request.POST, instance=account) if form.is_valid(): form.save() messages.success(request, _("Account saved successfully.")) return redirect('chart_of_accounts') else: form = AccountForm(instance=account) return render(request, 'accounting/account_form.html', {'form': form, 'account': account}) @login_required def journal_entries(request): entries = JournalEntry.objects.annotate( total_debit=Coalesce(Sum('items__amount', filter=Q(items__type='debit')), Value(0), output_field=DecimalField()), total_credit=Coalesce(Sum('items__amount', filter=Q(items__type='credit')), Value(0), output_field=DecimalField()) ).prefetch_related('items__account').order_by('-date', '-id') return render(request, 'accounting/journal_entries.html', {'entries': entries}) @login_required def manual_journal_entry(request): accounts = Account.objects.filter(is_active=True).order_by('code') if request.method == 'POST': form = JournalEntryForm(request.POST) # Manual journal entry requires at least two items and they must balance account_ids = request.POST.getlist('account[]') types = request.POST.getlist('type[]') amounts = request.POST.getlist('amount[]') if form.is_valid(): try: with transaction.atomic(): entry = form.save() total_debit = 0 total_credit = 0 for i in range(len(account_ids)): acc_id = account_ids[i] item_type = types[i] amount = float(amounts[i]) if amount <= 0: continue JournalItem.objects.create( entry=entry, account_id=acc_id, type=item_type, amount=amount ) if item_type == 'debit': total_debit += amount else: total_credit += amount if round(total_debit, 3) != round(total_credit, 3): raise Exception(f"Journal entry does not balance. Total Debit: {total_debit}, Total Credit: {total_credit}") if total_debit == 0: raise Exception("Journal entry must have at least one debit and one credit.") messages.success(request, _("Manual journal entry created successfully.")) return redirect('journal_entries') except Exception as e: messages.error(request, str(e)) else: form = JournalEntryForm() return render(request, 'accounting/journal_entry_form.html', { 'form': form, 'accounts': accounts }) @login_required def account_ledger(request, account_id): account = get_object_or_404(Account, id=account_id) items = JournalItem.objects.filter(account=account).order_by('entry__date', 'entry__id') # Calculate running balance running_balance = 0 ledger_items = [] for item in items: if account.account_type in ['asset', 'expense']: change = item.amount if item.type == 'debit' else -item.amount else: change = item.amount if item.type == 'credit' else -item.amount running_balance += change ledger_items.append({ 'item': item, 'balance': running_balance }) return render(request, 'accounting/account_ledger.html', { 'account': account, 'ledger_items': ledger_items, 'total_balance': running_balance }) @login_required def trial_balance(request): accounts = Account.objects.all().order_by('code') trial_data = [] total_debit = 0 total_credit = 0 for acc in accounts: items = acc.journal_items.all() debits = items.filter(type='debit').aggregate(total=Sum('amount'))['total'] or 0 credits = items.filter(type='credit').aggregate(total=Sum('amount'))['total'] or 0 if debits > 0 or credits > 0: trial_data.append({ 'account': acc, 'debit': debits, 'credit': credits }) total_debit += debits total_credit += credits return render(request, 'accounting/trial_balance.html', { 'trial_data': trial_data, 'total_debit': total_debit, 'total_credit': total_credit }) @login_required def balance_sheet(request): assets = Account.objects.filter(account_type='asset') liabilities = Account.objects.filter(account_type='liability') equity = Account.objects.filter(account_type='equity') # Include Net Income in Equity revenue = JournalItem.objects.filter(account__account_type='income').aggregate( cr=Sum('amount', filter=Q(type='credit')), dr=Sum('amount', filter=Q(type='debit')) ) net_revenue = (revenue['cr'] or 0) - (revenue['dr'] or 0) expenses = JournalItem.objects.filter(account__account_type='expense').aggregate( dr=Sum('amount', filter=Q(type='debit')), cr=Sum('amount', filter=Q(type='credit')) ) net_expenses = (expenses['dr'] or 0) - (expenses['cr'] or 0) net_income = net_revenue - net_expenses asset_total = sum(acc.balance for acc in assets) liability_total = sum(acc.balance for acc in liabilities) equity_total = sum(acc.balance for acc in equity) + net_income return render(request, 'accounting/balance_sheet.html', { 'assets': assets, 'liabilities': liabilities, 'equity': equity, 'net_income': net_income, 'asset_total': asset_total, 'liability_total': liability_total, 'equity_total': equity_total, 'date': timezone.now() }) @login_required def profit_loss(request): revenue_accounts = Account.objects.filter(account_type='income') expense_accounts = Account.objects.filter(account_type='expense') revenue_total = sum(acc.balance for acc in revenue_accounts) expense_total = sum(acc.balance for acc in expense_accounts) return render(request, 'accounting/profit_loss.html', { 'revenue_accounts': revenue_accounts, 'expense_accounts': expense_accounts, 'revenue_total': revenue_total, 'expense_total': expense_total, 'net_profit': revenue_total - expense_total, 'date': timezone.now() })