Initial commit
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
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",
|
||||
})
|
||||
Reference in New Issue
Block a user