diff --git a/README.md b/README.md index 03fe0c9..b6df897 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Nyckel-URLer (språkprefixed): - **Auto-prefill:** Inloggade användare får namn, e-post och senaste kontonummer förifyllt. - **Valuta & projekt:** Varje rad har dold valutaväljare (SEK default) och projektreferens. Projekt listas från Django admin > Projekt. - **Kvitton:** Filuppladdningar sparas med slumpat UUID-baserat namn under `receipts/` för säkerhet och unika namn. -- **Dashboard:** KPI-kort med totalsiffror, senaste aktivitet, statusfördelning och samma inline-flöde för beslut/utbetalningar. +- **Dashboard:** KPI-kort med totalsiffror, senaste aktivitet, statusfördelning och samma inline-flöde för beslut/utbetalningar (inkl. möjlighet att korrigera projekt vid beslut). - **Betalspårning:** När intern betalning är på får godkända claims en "Betala"-knapp. När ett claim markeras som betalt låses status/kommentar tills reset görs. - **Mina utlägg:** Inloggade ser sina egna claims i samma Tailwind-layout med kvitto-länk och logg. - **Användarhantering:** Tailwind-sida där personal kan skapa konton, tilldela `claims.view_claim`/`claims.change_claim`, markera staff och ta bort användare. diff --git a/claims/forms.py b/claims/forms.py index 283fa99..b71b1bf 100644 --- a/claims/forms.py +++ b/claims/forms.py @@ -67,6 +67,15 @@ class ClaimDecisionForm(forms.Form): claim_id = forms.IntegerField(widget=forms.HiddenInput) action = forms.ChoiceField(choices=ACTION_CHOICES) + project = forms.ModelChoiceField( + queryset=Project.objects.none(), + required=False, + label=_("Evenemang/Projekt"), + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["project"].queryset = Project.objects.filter(is_active=True).order_by("name") decision_note = forms.CharField( required=False, widget=forms.Textarea(attrs={"rows": 2, "placeholder": _("Kommentar")}), diff --git a/claims/templates/claims/dashboard.html b/claims/templates/claims/dashboard.html index 6233333..dd738b2 100644 --- a/claims/templates/claims/dashboard.html +++ b/claims/templates/claims/dashboard.html @@ -258,11 +258,20 @@ {% endfor %} - - + + - - diff --git a/claims/tests.py b/claims/tests.py index bbd0be9..7fc67ac 100644 --- a/claims/tests.py +++ b/claims/tests.py @@ -8,7 +8,8 @@ from django.test import TestCase, override_settings from django.urls import reverse from django.utils import timezone -from .models import Claim +from .forms import ClaimDecisionForm +from .models import Claim, Project from .validators import validate_receipt_file from .views import SubmitClaimView @@ -79,7 +80,8 @@ class DashboardViewTests(TestCase): User = get_user_model() self.user = User.objects.create_user(username="admin", password="test123", email="admin@example.com") view_perm = Permission.objects.get(codename="view_claim") - self.user.user_permissions.add(view_perm) + change_perm = Permission.objects.get(codename="change_claim") + self.user.user_permissions.add(view_perm, change_perm) self.client.force_login(self.user) def _create_claim(self, **kwargs): @@ -122,3 +124,23 @@ class DashboardViewTests(TestCase): self._create_claim(status=Claim.Status.PENDING) response = self.client.get(reverse("claims:admin-list") + "?status=approved") self.assertFalse(response.context["has_filtered_claims"]) + + def test_attester_can_update_project_when_deciding(self): + project_old = Project.objects.create(name="Original", is_active=True) + project_new = Project.objects.create(name="Corrected", is_active=True) + claim = self._create_claim(project=project_old) + + response = self.client.post( + reverse("claims:admin-list"), + { + "action_type": "decision", + "claim_id": claim.id, + "action": ClaimDecisionForm.ACTION_APPROVE, + "decision_note": "Updated project", + "project": project_new.id, + }, + follow=True, + ) + self.assertEqual(response.status_code, 200) + claim.refresh_from_db() + self.assertEqual(claim.project, project_new) diff --git a/claims/views.py b/claims/views.py index d154e42..e556eca 100644 --- a/claims/views.py +++ b/claims/views.py @@ -24,7 +24,7 @@ from .forms import ( UserPermissionForm, ) from .email_utils import notify_admin_of_claim, send_claimant_confirmation_email -from .models import Claim, ClaimLog +from .models import Claim, ClaimLog, Project User = get_user_model() @@ -161,6 +161,7 @@ class ClaimDashboardView(LoginRequiredMixin, PermissionRequiredMixin, ListView): context["can_change"] = self.request.user.has_perm("claims.change_claim") context["payments_enabled"] = getattr(settings, "CLAIMS_ENABLE_INTERNAL_PAYMENTS", False) context["summary"] = self._build_summary() + context["project_options"] = Project.objects.filter(is_active=True).order_by("name") context["has_any_claims"] = context["summary"]["total_claims"] > 0 context["has_filtered_claims"] = self._has_filtered_claims(context["status_filter"], context["summary"]) context["recent_claims"] = ( @@ -196,6 +197,11 @@ class ClaimDashboardView(LoginRequiredMixin, PermissionRequiredMixin, ListView): return redirect(request.get_full_path()) previous_status = claim.status claim.decision_note = decision_note + new_project = form.cleaned_data.get("project") + project_changed = False + if new_project is not None and new_project != claim.project: + claim.project = new_project + project_changed = True if action == ClaimDecisionForm.ACTION_APPROVE: claim.status = Claim.Status.APPROVED @@ -204,7 +210,10 @@ class ClaimDashboardView(LoginRequiredMixin, PermissionRequiredMixin, ListView): claim.status = Claim.Status.REJECTED messages.warning(request, _("%(claim)s markerades som nekad.") % {"claim": claim}) - claim.save(update_fields=["status", "decision_note", "updated_at"]) + update_fields = ["status", "decision_note", "updated_at"] + if project_changed: + update_fields.append("project") + claim.save(update_fields=update_fields) claim.add_log( action=ClaimLog.Action.STATUS_CHANGED, performed_by=request.user, diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo index d97c1c8..1f2c5a6 100644 Binary files a/locale/en/LC_MESSAGES/django.mo and b/locale/en/LC_MESSAGES/django.mo differ diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index f373ee4..30d2986 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: claims-system 0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-09 13:13+0100\n" +"POT-Creation-Date: 2025-11-09 13:48+0100\n" "PO-Revision-Date: 2025-11-08 23:40+0100\n" "Last-Translator: ChatGPT \n" "Language-Team: English\n" @@ -16,7 +16,7 @@ msgstr "" msgid "Namn" msgstr "Name" -#: claims/forms.py:23 claims/forms.py:86 +#: claims/forms.py:23 claims/forms.py:95 #: claims/templates/claims/dashboard.html:169 msgid "E-post" msgstr "Email" @@ -38,7 +38,8 @@ msgstr "Amount" msgid "Valuta" msgstr "Currency" -#: claims/forms.py:52 +#: claims/forms.py:52 claims/forms.py:73 +#: claims/templates/claims/dashboard.html:264 msgid "Evenemang/Projekt" msgstr "Project" @@ -54,76 +55,76 @@ msgstr "Approve" msgid "Neka" msgstr "Reject" -#: claims/forms.py:72 claims/templates/claims/dashboard.html:126 +#: claims/forms.py:81 claims/templates/claims/dashboard.html:126 #: claims/templates/claims/dashboard.html:261 msgid "Kommentar" msgstr "Comment" -#: claims/forms.py:80 +#: claims/forms.py:89 msgid "Kommentar krävs när du nekar ett utlägg." msgstr "A comment is required when you reject an expense." -#: claims/forms.py:85 +#: claims/forms.py:94 msgid "Användarnamn" msgstr "Username" -#: claims/forms.py:87 +#: claims/forms.py:96 msgid "Förnamn" msgstr "First name" -#: claims/forms.py:88 +#: claims/forms.py:97 msgid "Efternamn" msgstr "Last name" -#: claims/forms.py:89 +#: claims/forms.py:98 msgid "Lösenord" msgstr "Password" -#: claims/forms.py:90 +#: claims/forms.py:99 msgid "Bekräfta lösenord" msgstr "Confirm password" -#: claims/forms.py:91 +#: claims/forms.py:100 msgid "Administratör (staff)" msgstr "Administrator (staff)" -#: claims/forms.py:92 +#: claims/forms.py:101 msgid "Ge behörighet att se utlägg" msgstr "Allow viewing claims" -#: claims/forms.py:93 +#: claims/forms.py:102 msgid "Ge behörighet att besluta utlägg" msgstr "Allow deciding claims" -#: claims/forms.py:98 +#: claims/forms.py:107 msgid "Användarnamnet är upptaget." msgstr "That username is already taken." -#: claims/forms.py:104 +#: claims/forms.py:113 msgid "Lösenorden matchar inte." msgstr "Passwords do not match." -#: claims/forms.py:122 claims/templates/claims/user_management.html:116 +#: claims/forms.py:131 claims/templates/claims/user_management.html:116 msgid "Admin/staff" msgstr "Admin/staff" -#: claims/forms.py:123 claims/templates/claims/user_management.html:120 +#: claims/forms.py:132 claims/templates/claims/user_management.html:120 msgid "Får se utlägg" msgstr "May view claims" -#: claims/forms.py:124 claims/templates/claims/user_management.html:124 +#: claims/forms.py:133 claims/templates/claims/user_management.html:124 msgid "Får besluta utlägg" msgstr "May decide claims" -#: claims/models.py:29 claims/templates/claims/dashboard.html:325 +#: claims/models.py:29 claims/templates/claims/dashboard.html:334 msgid "Pending" msgstr "Pending" -#: claims/models.py:30 claims/templates/claims/dashboard.html:329 +#: claims/models.py:30 claims/templates/claims/dashboard.html:338 msgid "Approved" msgstr "Approved" -#: claims/models.py:31 claims/templates/claims/dashboard.html:333 +#: claims/models.py:31 claims/templates/claims/dashboard.html:342 msgid "Rejected" msgstr "Rejected" @@ -241,7 +242,9 @@ msgstr "Track inflow, decisions, and payouts – and act on claims immediately." msgid "" "Tips: använd filtren för att fokusera på specifika statusar eller projekt. " "Dashboarden uppdateras i realtid när data ändras." -msgstr "Tip: use the filters to focus on statuses or projects. The dashboard updates in real time." +msgstr "" +"Tip: use the filters to focus on statuses or projects. The dashboard updates " +"in real time." #: claims/templates/claims/dashboard.html:21 msgid "Totalt antal utlägg" @@ -366,7 +369,9 @@ msgstr "Project" msgid "" "Använd referensen och beloppet när du lägger upp betalningen – hjälper att " "undvika dubbletter." -msgstr "Use the reference and amount when entering the payment – it helps avoid duplicates." +msgstr "" +"Use the reference and amount when entering the payment – it helps avoid " +"duplicates." #: claims/templates/claims/dashboard.html:182 msgid "" @@ -434,43 +439,51 @@ msgid "Åtgärd" msgstr "Action" #: claims/templates/claims/dashboard.html:266 +msgid "Behåll nuvarande" +msgstr "Keep current" + +#: claims/templates/claims/dashboard.html:271 +msgid "Justera projekt om underlaget skickats in mot fel evenemang." +msgstr "Adjust the project if the submission was sent against the wrong event." + +#: claims/templates/claims/dashboard.html:275 msgid "Uppdatera beslut" msgstr "Update decision" -#: claims/templates/claims/dashboard.html:277 +#: claims/templates/claims/dashboard.html:286 #: claims/templates/claims/my_claims.html:78 msgid "Inga utlägg ännu" msgstr "No claims yet" -#: claims/templates/claims/dashboard.html:278 +#: claims/templates/claims/dashboard.html:287 msgid "När formuläret tas emot visas posterna automatiskt här." msgstr "As soon as submissions arrive they will appear here." -#: claims/templates/claims/dashboard.html:284 +#: claims/templates/claims/dashboard.html:293 msgid "Inga utlägg matchar filtret" msgstr "No claims match the filter" -#: claims/templates/claims/dashboard.html:285 +#: claims/templates/claims/dashboard.html:294 msgid "Välj en annan status för att se fler poster." msgstr "Choose another status to see more entries." -#: claims/templates/claims/dashboard.html:293 +#: claims/templates/claims/dashboard.html:302 msgid "Senaste inskick" msgstr "Latest submissions" -#: claims/templates/claims/dashboard.html:294 +#: claims/templates/claims/dashboard.html:303 msgid "Aktivitet" msgstr "Activity" -#: claims/templates/claims/dashboard.html:312 +#: claims/templates/claims/dashboard.html:321 msgid "Inga aktiviteter än." msgstr "No activity yet." -#: claims/templates/claims/dashboard.html:320 +#: claims/templates/claims/dashboard.html:329 msgid "Statusfördelning" msgstr "Status breakdown" -#: claims/templates/claims/dashboard.html:321 +#: claims/templates/claims/dashboard.html:330 msgid "Snabbstatistik" msgstr "Quick stats" @@ -827,87 +840,87 @@ msgstr "" msgid "Kunde inte spara utläggen. Kontrollera formuläret." msgstr "" -#: claims/views.py:181 claims/views.py:222 +#: claims/views.py:182 claims/views.py:231 #, fuzzy #| msgid "Ge behörighet att besluta utlägg" msgid "Du har inte behörighet att uppdatera utlägg." msgstr "Allow deciding claims" -#: claims/views.py:195 +#: claims/views.py:196 #, fuzzy #| msgid "" #| "Utlägget är markerat som betalt. Ändringar av beslut/kommentar är låsta." msgid "Utlägget är redan markerat som betalt och kan inte ändras." msgstr "The claim is marked as paid. Decision/comments are locked." -#: claims/views.py:202 +#: claims/views.py:208 #, python-format msgid "%(claim)s markerades som godkänd." msgstr "" -#: claims/views.py:205 +#: claims/views.py:211 #, fuzzy, python-format #| msgid "Ej markerad som betald" msgid "%(claim)s markerades som nekad." msgstr "Not marked as paid" -#: claims/views.py:219 +#: claims/views.py:228 msgid "Betalningshantering är inte aktiverad." msgstr "" -#: claims/views.py:227 +#: claims/views.py:236 msgid "Endast godkända utlägg kan markeras som betalda." msgstr "" -#: claims/views.py:230 +#: claims/views.py:239 msgid "Detta utlägg är redan markerat som betalt." msgstr "" -#: claims/views.py:241 +#: claims/views.py:250 #, fuzzy, python-format #| msgid "Ej markerad som betald" msgid "%(claim)s markerades som betald." msgstr "Not marked as paid" -#: claims/views.py:307 +#: claims/views.py:316 msgid "Du saknar behörighet för åtgärden." msgstr "" -#: claims/views.py:354 +#: claims/views.py:363 #, python-format msgid "Användaren %(user)s skapades." msgstr "" -#: claims/views.py:365 +#: claims/views.py:374 msgid "Du kan inte ta bort din egen staff-status." msgstr "" -#: claims/views.py:371 +#: claims/views.py:380 #, python-format msgid "Behörigheter uppdaterades för %(user)s." msgstr "" -#: claims/views.py:373 +#: claims/views.py:382 #, fuzzy #| msgid "Justera behörigheter" msgid "Kunde inte uppdatera behörigheter." msgstr "Adjust permissions" -#: claims/views.py:383 +#: claims/views.py:392 msgid "Du kan inte ta bort ditt eget konto." msgstr "" -#: claims/views.py:385 +#: claims/views.py:394 msgid "Du kan inte ta bort en superuser via detta gränssnitt." msgstr "" -#: claims/views.py:388 +#: claims/views.py:397 #, fuzzy #| msgid "Användare" msgid "Användaren togs bort." msgstr "Users" -#: claims/views.py:391 +#: claims/views.py:400 msgid "Okänd åtgärd." msgstr ""