Files
ProjectApp/finance/views.py
T
Felix Baumann fb47c53d52 Initial commit
2026-06-27 17:07:34 +00:00

331 lines
8.3 KiB
Python

from django.contrib.auth.decorators import login_required
from accounts.decorators import permission_required
from django.shortcuts import render, get_object_or_404, redirect
from django.db.models.functions import TruncMonth, TruncYear, TruncWeek, TruncDay
from django.db.models import Sum
from datetime import date, datetime, timedelta
from projects.models import ProjectMembership
from .models import Income, Expense
import csv
from django.http import HttpResponse
from calendar import monthrange
from .forms import IncomeForm, ExpenseForm
from .tables import PeriodAnalyticsTable, FinanceTransactionTable
# =========================================================
# OVERVIEW
# =========================================================
@login_required
@permission_required("projects_finance_view")
def finance_overview(request, project_id):
membership = get_object_or_404(
ProjectMembership,
user=request.user,
project_id=project_id
)
project = membership.project
# -------------------------
# DATE FILTER (for KPI ONLY)
# -------------------------
start = request.GET.get("start")
end = request.GET.get("end")
if not start or not end:
start_date = end_date = date.today()
else:
start_date = datetime.strptime(start, "%Y-%m-%d").date()
end_date = datetime.strptime(end, "%Y-%m-%d").date()
# -------------------------
# KPI DATA (FILTERED)
# -------------------------
filtered_incomes = Income.objects.filter(
project=project,
date__date__range=[start_date, end_date]
)
filtered_expenses = Expense.objects.filter(
project=project,
date__date__range=[start_date, end_date]
)
total_income = sum(i.amount for i in filtered_incomes)
total_expense = sum(e.amount for e in filtered_expenses)
total_profit = total_income - total_expense
incomes = Income.objects.filter(
project=project,
date__date__range=[start_date, end_date]
).order_by("-date")
expenses = Expense.objects.filter(
project=project,
date__date__range=[start_date, end_date]
).order_by("-date")
# -------------------------
# PERIOD VIEW
# -------------------------
period = request.GET.get("period", "year")
if period == "year":
trunc = TruncYear("date")
elif period == "week":
trunc = TruncWeek("date")
elif period == "day":
trunc = TruncDay("date")
else:
trunc = TruncMonth("date")
income_stats = (
Income.objects.filter(project=project)
.annotate(period_key=trunc)
.values("period_key")
.annotate(total=Sum("amount"))
.order_by("-period_key")
)
expense_stats = (
Expense.objects.filter(project=project)
.annotate(period_key=trunc)
.values("period_key")
.annotate(total=Sum("amount"))
.order_by("-period_key")
)
expense_map = {
str(e["period_key"]): e["total"]
for e in expense_stats
}
period_data = []
for i in income_stats:
period_start = i["period_key"]
income_total = i["total"] or 0
expense_total = expense_map.get(str(period_start), 0)
if period == "year":
period_end = period_start.replace(month=12, day=31)
elif period == "week":
period_end = period_start + timedelta(days=6)
elif period == "day":
period_end = period_start
else:
last_day = monthrange(period_start.year, period_start.month)[1]
period_end = period_start.replace(day=last_day)
period_data.append({
"period": period_start,
"period_start": period_start,
"period_end": period_end,
"income": income_total,
"expense": expense_total,
"profit": income_total - expense_total,
})
# -------------------------
# RETURN
# -------------------------
table = PeriodAnalyticsTable(
period_data,
period_type=period,
request=request,
)
return render(request, "finance/overview.html", {
"project": project,
# KPI (filtered)
"total_income": total_income,
"total_expense": total_expense,
"total_profit": total_profit,
# LISTS (all data)
"incomes": incomes,
"expenses": expenses,
# PERIOD
"period_data": period_data,
"period": period,
# UI
"start_date": start_date,
"end_date": end_date,
"table": table,
})
@login_required
@permission_required("projects_finance_view")
def finance_view(request, project_id):
membership = get_object_or_404(
ProjectMembership,
user=request.user,
project_id=project_id
)
project = membership.project
start = request.GET.get("start")
end = request.GET.get("end")
incomes = Income.objects.filter(project=project)
expenses = Expense.objects.filter(project=project)
if start:
try:
start_date = datetime.strptime(start, "%Y-%m-%d").date()
incomes = incomes.filter(date__date__gte=start_date)
expenses = expenses.filter(date__date__gte=start_date)
except ValueError:
pass
if end:
try:
end_date = datetime.strptime(end, "%Y-%m-%d").date()
incomes = incomes.filter(date__date__lte=end_date)
expenses = expenses.filter(date__date__lte=end_date)
except ValueError:
pass
# -------------------------
# MERGED LIST (LIKE EXPORT ORDER)
# -------------------------
transactions = []
for i in incomes:
transactions.append({
"type": "Einnahme",
"date": i.date,
"amount": i.amount,
"category": i.category,
"name": i.name,
"created_by": i.created_by,
"note": i.note,
})
for e in expenses:
transactions.append({
"type": "Ausgabe",
"date": e.date,
"amount": e.amount,
"category": e.category,
"name": e.name,
"created_by": e.created_by,
"note": e.note,
"supplier": e.supplier,
"payment_method": e.payment_method,
"receipt": e.receipt,
})
transactions.sort(
key=lambda x: x["date"],
reverse=True,
)
table = FinanceTransactionTable(
transactions,
request=request,
)
return render(
request,
"finance/view.html",
{
"project": project,
"table": table,
"start_date": start_date,
"end_date": end_date,
},
)
# =========================
# INCOME CREATE
# =========================
@login_required
@permission_required("projects_finance_income_add")
def add_income(request, project_id):
membership = get_object_or_404(
ProjectMembership,
user=request.user,
project_id=project_id
)
project = membership.project
if request.method == "POST":
form = IncomeForm(request.POST)
if form.is_valid():
income = form.save(commit=False)
income.project = project
income.created_by = request.user
income.save()
return redirect("finance:overview", project_id=project.id)
else:
form = IncomeForm()
return render(request, "finance/form.html", {
"form": form,
"project": project,
"type": "income",
})
# =========================
# EXPENSE CREATE
# =========================
@login_required
@permission_required("projects_finance_expense_add")
def add_expense(request, project_id):
membership = get_object_or_404(
ProjectMembership,
user=request.user,
project_id=project_id
)
project = membership.project
if request.method == "POST":
form = ExpenseForm(request.POST, request.FILES)
if form.is_valid():
expense = form.save(commit=False)
expense.project = project
expense.created_by = request.user
expense.save()
return redirect("finance:overview", project_id=project.id)
else:
form = ExpenseForm()
return render(request, "finance/form.html", {
"form": form,
"project": project,
"type": "expense",
})