444 lines
15 KiB
Python
444 lines
15 KiB
Python
import logging
|
|
import os
|
|
|
|
from django.conf import settings
|
|
from django.contrib import messages
|
|
from django.contrib.auth.models import User
|
|
from django.core.files import File
|
|
from django.db.models import Count, Q, Sum
|
|
from django.shortcuts import get_object_or_404, redirect
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from django.views.generic import DetailView, ListView
|
|
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
|
|
|
from camps.mixins import CampViewMixin
|
|
from economy.models import Chain, Credebtor, Expense, Reimbursement, Revenue
|
|
from teams.models import Team
|
|
|
|
from ..mixins import EconomyTeamPermissionMixin
|
|
|
|
logger = logging.getLogger("bornhack.%s" % __name__)
|
|
|
|
|
|
################################
|
|
# CHAINS & CREDEBTORS
|
|
|
|
|
|
class ChainListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
|
|
model = Chain
|
|
template_name = "chain_list_backoffice.html"
|
|
|
|
def get_queryset(self, *args, **kwargs):
|
|
"""Annotate the total count and amount for expenses and revenues for all credebtors in each chain."""
|
|
qs = Chain.objects.annotate(
|
|
camp_expenses_amount=Sum(
|
|
"credebtors__expenses__amount",
|
|
filter=Q(credebtors__expenses__camp=self.camp),
|
|
distinct=True,
|
|
),
|
|
camp_expenses_count=Count(
|
|
"credebtors__expenses",
|
|
filter=Q(credebtors__expenses__camp=self.camp),
|
|
distinct=True,
|
|
),
|
|
camp_revenues_amount=Sum(
|
|
"credebtors__revenues__amount",
|
|
filter=Q(credebtors__revenues__camp=self.camp),
|
|
distinct=True,
|
|
),
|
|
camp_revenues_count=Count(
|
|
"credebtors__revenues",
|
|
filter=Q(credebtors__revenues__camp=self.camp),
|
|
distinct=True,
|
|
),
|
|
)
|
|
return qs
|
|
|
|
|
|
class ChainDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView):
|
|
model = Chain
|
|
template_name = "chain_detail_backoffice.html"
|
|
slug_url_kwarg = "chain_slug"
|
|
|
|
def get_queryset(self, *args, **kwargs):
|
|
"""Annotate the Chain object with the camp filtered expense and revenue info."""
|
|
qs = super().get_queryset(*args, **kwargs)
|
|
qs = qs.annotate(
|
|
camp_expenses_amount=Sum(
|
|
"credebtors__expenses__amount",
|
|
filter=Q(credebtors__expenses__camp=self.camp),
|
|
distinct=True,
|
|
),
|
|
camp_expenses_count=Count(
|
|
"credebtors__expenses",
|
|
filter=Q(credebtors__expenses__camp=self.camp),
|
|
distinct=True,
|
|
),
|
|
camp_revenues_amount=Sum(
|
|
"credebtors__revenues__amount",
|
|
filter=Q(credebtors__revenues__camp=self.camp),
|
|
distinct=True,
|
|
),
|
|
camp_revenues_count=Count(
|
|
"credebtors__revenues",
|
|
filter=Q(credebtors__revenues__camp=self.camp),
|
|
distinct=True,
|
|
),
|
|
)
|
|
return qs
|
|
|
|
def get_context_data(self, *args, **kwargs):
|
|
"""Add credebtors, expenses and revenues to the context in camp-filtered versions."""
|
|
context = super().get_context_data(*args, **kwargs)
|
|
|
|
# include credebtors as a seperate queryset with annotations for total number and
|
|
# amount of expenses and revenues
|
|
context["credebtors"] = Credebtor.objects.filter(
|
|
chain=self.get_object()
|
|
).annotate(
|
|
camp_expenses_amount=Sum(
|
|
"expenses__amount", filter=Q(expenses__camp=self.camp), distinct=True
|
|
),
|
|
camp_expenses_count=Count(
|
|
"expenses", filter=Q(expenses__camp=self.camp), distinct=True
|
|
),
|
|
camp_revenues_amount=Sum(
|
|
"revenues__amount", filter=Q(revenues__camp=self.camp), distinct=True
|
|
),
|
|
camp_revenues_count=Count(
|
|
"revenues", filter=Q(revenues__camp=self.camp), distinct=True
|
|
),
|
|
)
|
|
|
|
# Include expenses and revenues for the Chain in context as seperate querysets,
|
|
# since accessing them through the relatedmanager returns for all camps
|
|
context["expenses"] = Expense.objects.filter(
|
|
camp=self.camp, creditor__chain=self.get_object()
|
|
).prefetch_related("responsible_team", "user", "creditor")
|
|
context["revenues"] = Revenue.objects.filter(
|
|
camp=self.camp, debtor__chain=self.get_object()
|
|
).prefetch_related("responsible_team", "user", "debtor")
|
|
return context
|
|
|
|
|
|
class CredebtorDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView):
|
|
model = Credebtor
|
|
template_name = "credebtor_detail_backoffice.html"
|
|
slug_url_kwarg = "credebtor_slug"
|
|
|
|
def get_context_data(self, *args, **kwargs):
|
|
context = super().get_context_data(*args, **kwargs)
|
|
context["expenses"] = (
|
|
self.get_object()
|
|
.expenses.filter(camp=self.camp)
|
|
.prefetch_related("responsible_team", "user", "creditor")
|
|
)
|
|
context["revenues"] = (
|
|
self.get_object()
|
|
.revenues.filter(camp=self.camp)
|
|
.prefetch_related("responsible_team", "user", "debtor")
|
|
)
|
|
return context
|
|
|
|
|
|
################################
|
|
# EXPENSES
|
|
|
|
|
|
class ExpenseListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
|
|
model = Expense
|
|
template_name = "expense_list_backoffice.html"
|
|
|
|
def get_queryset(self, **kwargs):
|
|
"""
|
|
Exclude unapproved expenses, they are shown seperately
|
|
"""
|
|
queryset = super().get_queryset(**kwargs)
|
|
return queryset.exclude(approved__isnull=True).prefetch_related(
|
|
"creditor",
|
|
"user",
|
|
"responsible_team",
|
|
)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""
|
|
Include unapproved expenses seperately
|
|
"""
|
|
context = super().get_context_data(**kwargs)
|
|
context["unapproved_expenses"] = Expense.objects.filter(
|
|
camp=self.camp, approved__isnull=True
|
|
).prefetch_related(
|
|
"creditor",
|
|
"user",
|
|
"responsible_team",
|
|
)
|
|
return context
|
|
|
|
|
|
class ExpenseDetailView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
|
|
model = Expense
|
|
template_name = "expense_detail_backoffice.html"
|
|
fields = ["notes"]
|
|
|
|
def form_valid(self, form):
|
|
"""
|
|
We have two submit buttons in this form, Approve and Reject
|
|
"""
|
|
expense = form.save()
|
|
if "approve" in form.data:
|
|
# approve button was pressed
|
|
expense.approve(self.request)
|
|
elif "reject" in form.data:
|
|
# reject button was pressed
|
|
expense.reject(self.request)
|
|
else:
|
|
messages.error(self.request, "Unknown submit action")
|
|
return redirect(
|
|
reverse("backoffice:expense_list", kwargs={"camp_slug": self.camp.slug})
|
|
)
|
|
|
|
|
|
######################################
|
|
# REIMBURSEMENTS
|
|
|
|
|
|
class ReimbursementListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
|
|
model = Reimbursement
|
|
template_name = "reimbursement_list_backoffice.html"
|
|
|
|
|
|
class ReimbursementDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView):
|
|
model = Reimbursement
|
|
template_name = "reimbursement_detail_backoffice.html"
|
|
|
|
|
|
class ReimbursementCreateUserSelectView(
|
|
CampViewMixin, EconomyTeamPermissionMixin, ListView
|
|
):
|
|
template_name = "reimbursement_create_userselect.html"
|
|
|
|
def get_queryset(self):
|
|
queryset = User.objects.filter(
|
|
id__in=Expense.objects.filter(
|
|
camp=self.camp,
|
|
reimbursement__isnull=True,
|
|
paid_by_bornhack=False,
|
|
approved=True,
|
|
)
|
|
.values_list("user", flat=True)
|
|
.distinct()
|
|
)
|
|
return queryset
|
|
|
|
|
|
class ReimbursementCreateView(CampViewMixin, EconomyTeamPermissionMixin, CreateView):
|
|
model = Reimbursement
|
|
template_name = "reimbursement_create.html"
|
|
fields = ["notes", "paid"]
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
"""Get the user from kwargs"""
|
|
self.reimbursement_user = get_object_or_404(User, pk=kwargs["user_id"])
|
|
|
|
# get response now so we have self.camp available below
|
|
response = super().dispatch(request, *args, **kwargs)
|
|
|
|
# return the response
|
|
return response
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
# does this user have any approved and un-reimbursed expenses?
|
|
if not self.reimbursement_user.expenses.filter(
|
|
reimbursement__isnull=True, approved=True, paid_by_bornhack=False
|
|
):
|
|
messages.error(
|
|
request, "This user has no approved and unreimbursed expenses!"
|
|
)
|
|
return redirect(
|
|
reverse("backoffice:index", kwargs={"camp_slug": self.camp.slug})
|
|
)
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["expenses"] = Expense.objects.filter(
|
|
user=self.reimbursement_user,
|
|
approved=True,
|
|
reimbursement__isnull=True,
|
|
paid_by_bornhack=False,
|
|
)
|
|
context["total_amount"] = context["expenses"].aggregate(Sum("amount"))
|
|
context["reimbursement_user"] = self.reimbursement_user
|
|
return context
|
|
|
|
def form_valid(self, form):
|
|
"""
|
|
Set user and camp for the Reimbursement before saving
|
|
"""
|
|
# get the expenses for this user
|
|
expenses = Expense.objects.filter(
|
|
user=self.reimbursement_user,
|
|
approved=True,
|
|
reimbursement__isnull=True,
|
|
paid_by_bornhack=False,
|
|
)
|
|
if not expenses:
|
|
messages.error(self.request, "No expenses found")
|
|
return redirect(
|
|
reverse(
|
|
"backoffice:reimbursement_list",
|
|
kwargs={"camp_slug": self.camp.slug},
|
|
)
|
|
)
|
|
|
|
# get the Economy team for this camp
|
|
try:
|
|
economyteam = Team.objects.get(
|
|
camp=self.camp, name=settings.ECONOMYTEAM_NAME
|
|
)
|
|
except Team.DoesNotExist:
|
|
messages.error(self.request, "No economy team found")
|
|
return redirect(
|
|
reverse(
|
|
"backoffice:reimbursement_list",
|
|
kwargs={"camp_slug": self.camp.slug},
|
|
)
|
|
)
|
|
|
|
# create reimbursement in database
|
|
reimbursement = form.save(commit=False)
|
|
reimbursement.reimbursement_user = self.reimbursement_user
|
|
reimbursement.user = self.request.user
|
|
reimbursement.camp = self.camp
|
|
reimbursement.save()
|
|
|
|
# add all expenses to reimbursement
|
|
for expense in expenses:
|
|
expense.reimbursement = reimbursement
|
|
expense.save()
|
|
|
|
# create expense for this reimbursement
|
|
expense = Expense()
|
|
expense.camp = self.camp
|
|
expense.user = self.request.user
|
|
expense.amount = reimbursement.amount
|
|
expense.description = "Payment of reimbursement %s to %s" % (
|
|
reimbursement.pk,
|
|
reimbursement.reimbursement_user,
|
|
)
|
|
expense.paid_by_bornhack = True
|
|
expense.responsible_team = economyteam
|
|
expense.approved = True
|
|
expense.reimbursement = reimbursement
|
|
expense.invoice_date = timezone.now()
|
|
expense.creditor = Credebtor.objects.get(name="Reimbursement")
|
|
expense.invoice.save(
|
|
"na.jpg",
|
|
File(
|
|
open(
|
|
os.path.join(settings.DJANGO_BASE_PATH, "static_src/img/na.jpg"),
|
|
"rb",
|
|
)
|
|
),
|
|
)
|
|
expense.save()
|
|
|
|
messages.success(
|
|
self.request,
|
|
"Reimbursement %s has been created with invoice_date %s"
|
|
% (reimbursement.pk, timezone.now()),
|
|
)
|
|
return redirect(
|
|
reverse(
|
|
"backoffice:reimbursement_detail",
|
|
kwargs={"camp_slug": self.camp.slug, "pk": reimbursement.pk},
|
|
)
|
|
)
|
|
|
|
|
|
class ReimbursementUpdateView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
|
|
model = Reimbursement
|
|
template_name = "reimbursement_form.html"
|
|
fields = ["notes", "paid"]
|
|
|
|
def get_success_url(self):
|
|
return reverse(
|
|
"backoffice:reimbursement_detail",
|
|
kwargs={"camp_slug": self.camp.slug, "pk": self.get_object().pk},
|
|
)
|
|
|
|
|
|
class ReimbursementDeleteView(CampViewMixin, EconomyTeamPermissionMixin, DeleteView):
|
|
model = Reimbursement
|
|
template_name = "reimbursement_delete.html"
|
|
fields = ["notes", "paid"]
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
response = super().dispatch(request, *args, **kwargs)
|
|
if self.get_object().paid:
|
|
messages.error(
|
|
request,
|
|
"This reimbursement has already been paid so it cannot be deleted",
|
|
)
|
|
return redirect(
|
|
reverse(
|
|
"backoffice:reimbursement_list",
|
|
kwargs={"camp_slug": self.camp.slug},
|
|
)
|
|
)
|
|
return response
|
|
|
|
|
|
################################
|
|
# REVENUES
|
|
|
|
|
|
class RevenueListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
|
|
model = Revenue
|
|
template_name = "revenue_list_backoffice.html"
|
|
|
|
def get_queryset(self, **kwargs):
|
|
"""
|
|
Exclude unapproved revenues, they are shown seperately
|
|
"""
|
|
queryset = super().get_queryset(**kwargs)
|
|
return queryset.exclude(approved__isnull=True).prefetch_related(
|
|
"debtor",
|
|
"user",
|
|
"responsible_team",
|
|
)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""
|
|
Include unapproved revenues seperately
|
|
"""
|
|
context = super().get_context_data(**kwargs)
|
|
context["unapproved_revenues"] = Revenue.objects.filter(
|
|
camp=self.camp, approved__isnull=True
|
|
)
|
|
return context
|
|
|
|
|
|
class RevenueDetailView(CampViewMixin, EconomyTeamPermissionMixin, UpdateView):
|
|
model = Revenue
|
|
template_name = "revenue_detail_backoffice.html"
|
|
fields = ["notes"]
|
|
|
|
def form_valid(self, form):
|
|
"""
|
|
We have two submit buttons in this form, Approve and Reject
|
|
"""
|
|
revenue = form.save()
|
|
if "approve" in form.data:
|
|
# approve button was pressed
|
|
revenue.approve(self.request)
|
|
elif "reject" in form.data:
|
|
# reject button was pressed
|
|
revenue.reject(self.request)
|
|
else:
|
|
messages.error(self.request, "Unknown submit action")
|
|
return redirect(
|
|
reverse("backoffice:revenue_list", kwargs={"camp_slug": self.camp.slug})
|
|
)
|