38086-vm/accounting/views.py
2026-02-08 06:12:20 +00:00

236 lines
9.1 KiB
Python

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()
})