Files
claims-system/claims/views.py
2025-11-08 16:54:46 +01:00

309 lines
12 KiB
Python

from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.forms import formset_factory
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.views import View
from django.views.generic import ListView, TemplateView
from .forms import (
ClaimDecisionForm,
ClaimLineForm,
ClaimantForm,
DeleteUserForm,
UserManagementForm,
UserPermissionForm,
)
from .models import Claim, ClaimLog
User = get_user_model()
class SubmitClaimView(View):
template_name = "claims/submit_claim.html"
max_extra_forms = 5
def get_extra_forms(self):
try:
count = int(self.request.GET.get("forms", 2))
except (TypeError, ValueError):
count = 2
return max(1, min(count, self.max_extra_forms))
def build_formset(self, *, data=None, files=None, extra=0):
FormSet = formset_factory(
ClaimLineForm,
extra=extra,
min_num=1,
validate_min=True,
)
return FormSet(data=data, files=files, prefix="claim_lines")
def get_claimant_initial(self):
initial = {}
user = self.request.user
if user.is_authenticated:
initial["full_name"] = user.get_full_name() or user.get_username()
initial["email"] = user.email
last_claim = user.claims_submitted.order_by("-created_at").first()
if last_claim:
initial["account_number"] = last_claim.account_number
return initial
def get(self, request):
extra = self.get_extra_forms()
formset = self.build_formset(extra=extra)
claimant_form = ClaimantForm(initial=self.get_claimant_initial())
return render(
request,
self.template_name,
{
"formset": formset,
"claimant_form": claimant_form,
"extra_forms": extra,
"max_extra_forms": self.max_extra_forms,
},
)
def post(self, request):
formset = self.build_formset(data=request.POST, files=request.FILES)
claimant_form = ClaimantForm(request.POST)
if formset.is_valid() and claimant_form.is_valid():
claimant_data = claimant_form.cleaned_data
created = 0
for form in formset:
if not form.cleaned_data:
continue
description = form.cleaned_data.get("description")
amount = form.cleaned_data.get("amount")
if not description or amount is None:
continue
claim = Claim(
full_name=claimant_data["full_name"],
email=claimant_data["email"],
account_number=claimant_data["account_number"],
amount=amount,
currency=form.cleaned_data.get("currency") or Claim.Currency.SEK,
description=description,
receipt=form.cleaned_data.get("receipt"),
project=form.cleaned_data.get("project"),
submitted_by=request.user if request.user.is_authenticated else None,
)
claim.save()
claim.add_log(
action=ClaimLog.Action.CREATED,
performed_by=claim.submitted_by,
to_status=Claim.Status.PENDING,
)
created += 1
if created:
messages.success(request, f"{created} utlägg skickade in.")
return redirect(reverse("claims:admin-list"))
messages.error(request, "Inga utlägg kunde sparas. Fyll i minst en rad.")
else:
messages.error(request, "Kunde inte spara utläggen. Kontrollera formuläret.")
extra = min(formset.total_form_count(), self.max_extra_forms)
return render(
request,
self.template_name,
{
"formset": formset,
"claimant_form": claimant_form,
"extra_forms": extra,
"max_extra_forms": self.max_extra_forms,
},
)
class ClaimAdminListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
template_name = "claims/admin_list.html"
context_object_name = "claims"
permission_required = "claims.view_claim"
def get_queryset(self):
queryset = (
Claim.objects.select_related("submitted_by", "project")
.prefetch_related("logs__performed_by")
.all()
)
status = self.request.GET.get("status")
if status in {choice[0] for choice in Claim.Status.choices}:
queryset = queryset.filter(status=status)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["status_filter"] = self.request.GET.get("status", "all")
context["status_choices"] = Claim.Status.choices
context["decision_choices"] = ClaimDecisionForm().fields["action"].choices
context["can_change"] = self.request.user.has_perm("claims.change_claim")
return context
def post(self, request, *args, **kwargs):
if not request.user.has_perm("claims.change_claim"):
messages.error(request, "Du har inte behörighet att uppdatera utlägg.")
return redirect(request.get_full_path())
form = ClaimDecisionForm(request.POST)
if not form.is_valid():
for field_errors in form.errors.values():
for error in field_errors:
messages.error(request, error)
return redirect(request.get_full_path())
claim = get_object_or_404(Claim, pk=form.cleaned_data["claim_id"])
action = form.cleaned_data["action"]
decision_note = form.cleaned_data.get("decision_note", "")
previous_status = claim.status
claim.decision_note = decision_note
if action == ClaimDecisionForm.ACTION_APPROVE:
claim.status = Claim.Status.APPROVED
messages.success(request, f"{claim} markerades som godkänd.")
else:
claim.status = Claim.Status.REJECTED
messages.warning(request, f"{claim} markerades som nekad.")
claim.save(update_fields=["status", "decision_note", "updated_at"])
claim.add_log(
action=ClaimLog.Action.STATUS_CHANGED,
performed_by=request.user,
from_status=previous_status,
to_status=claim.status,
note=decision_note,
)
return redirect(request.get_full_path())
class ClaimExportMenuView(LoginRequiredMixin, PermissionRequiredMixin, TemplateView):
template_name = "claims/export_placeholder.html"
permission_required = "claims.view_claim"
class MyClaimsView(LoginRequiredMixin, ListView):
template_name = "claims/my_claims.html"
context_object_name = "claims"
def get_queryset(self):
return (
Claim.objects.filter(submitted_by=self.request.user)
.select_related("project")
.prefetch_related("logs__performed_by")
.order_by("-created_at")
)
class UserManagementView(LoginRequiredMixin, PermissionRequiredMixin, TemplateView):
template_name = "claims/user_management.html"
permission_required = "auth.view_user"
def _ensure_perm(self, perm_codename):
perm = f"auth.{perm_codename}"
if not self.request.user.has_perm(perm):
messages.error(self.request, "Du saknar behörighet för åtgärden.")
return False
return True
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
users = User.objects.order_by("username")
rows = []
for user in users:
rows.append(
{
"user": user,
"permission_form": UserPermissionForm(
initial={
"user_id": user.id,
"is_staff": user.is_staff,
"grant_view": user.has_perm("claims.view_claim"),
"grant_change": user.has_perm("claims.change_claim"),
}
),
"delete_form": None
if user == self.request.user or user.is_superuser
else DeleteUserForm(initial={"user_id": user.id}),
}
)
context["user_rows"] = rows
context["create_form"] = kwargs.get("create_form") or UserManagementForm()
return context
def post(self, request, *args, **kwargs):
action = request.POST.get("action")
if action == "create":
if not self._ensure_perm("add_user"):
return redirect(request.path)
form = UserManagementForm(request.POST)
if form.is_valid():
user = User.objects.create_user(
username=form.cleaned_data["username"],
password=form.cleaned_data["password1"],
email=form.cleaned_data.get("email", ""),
first_name=form.cleaned_data.get("first_name", ""),
last_name=form.cleaned_data.get("last_name", ""),
is_staff=form.cleaned_data.get("is_staff", False),
)
self._set_perm(user, "claims.view_claim", form.cleaned_data.get("grant_view", False))
self._set_perm(user, "claims.change_claim", form.cleaned_data.get("grant_change", False))
messages.success(request, f"Användaren {user.username} skapades.")
return redirect(request.path)
return self.render_to_response(self.get_context_data(create_form=form))
elif action == "update":
if not self._ensure_perm("change_user"):
return redirect(request.path)
form = UserPermissionForm(request.POST)
if form.is_valid():
user = get_object_or_404(User, pk=form.cleaned_data["user_id"])
if user == request.user and not form.cleaned_data["is_staff"]:
messages.error(request, "Du kan inte ta bort din egen staff-status.")
return redirect(request.path)
user.is_staff = form.cleaned_data["is_staff"]
user.save(update_fields=["is_staff"])
self._set_perm(user, "claims.view_claim", form.cleaned_data["grant_view"])
self._set_perm(user, "claims.change_claim", form.cleaned_data["grant_change"])
messages.success(request, f"Behörigheter uppdaterades för {user.username}.")
else:
messages.error(request, "Kunde inte uppdatera behörigheter.")
return redirect(request.path)
elif action == "delete":
if not self._ensure_perm("delete_user"):
return redirect(request.path)
form = DeleteUserForm(request.POST)
if form.is_valid():
user = get_object_or_404(User, pk=form.cleaned_data["user_id"])
if user == request.user:
messages.error(request, "Du kan inte ta bort ditt eget konto.")
elif user.is_superuser:
messages.error(request, "Du kan inte ta bort en superuser via detta gränssnitt.")
else:
user.delete()
messages.warning(request, "Användaren togs bort.")
return redirect(request.path)
messages.error(request, "Okänd åtgärd.")
return redirect(request.path)
@staticmethod
def _set_perm(user, perm_label, should_have):
app_label, codename = perm_label.split(".")
perm = Permission.objects.filter(
content_type__app_label=app_label,
codename=codename,
).first()
if not perm:
return
if should_have:
user.user_permissions.add(perm)
else:
user.user_permissions.remove(perm)