diff --git a/fahari/common/tests/test_admin.py b/fahari/common/tests/test_admin.py index 9dddce99..e04942a0 100644 --- a/fahari/common/tests/test_admin.py +++ b/fahari/common/tests/test_admin.py @@ -1,5 +1,4 @@ import pytest -from django.conf import settings from django.contrib.admin.sites import AdminSite from model_bakery import baker @@ -10,15 +9,6 @@ pytestmark = pytest.mark.django_db -@pytest.fixture -def request_with_user(rf, django_user_model): - url = settings.ADMIN_URL + "/common/organisation/add/" - request = rf.get(url) - user = baker.make(django_user_model) - request.user = user - return request - - @pytest.fixture def organisation_admin(): admin = OrganisationAdmin(model=Organisation, admin_site=AdminSite()) diff --git a/fahari/conftest.py b/fahari/conftest.py index fa5cc976..2e69083b 100644 --- a/fahari/conftest.py +++ b/fahari/conftest.py @@ -1,4 +1,5 @@ import pytest +from django.conf import settings from django.contrib.auth.models import Group, Permission from model_bakery import baker @@ -53,3 +54,12 @@ def user_with_group(user, group_with_all_permissions) -> User: user.groups.add(group_with_all_permissions) user.save() return user + + +@pytest.fixture +def request_with_user(rf, django_user_model): + url = settings.ADMIN_URL + "/common/organisation/add/" + request = rf.get(url) + user = baker.make(django_user_model) + request.user = user + return request diff --git a/fahari/ops/models.py b/fahari/ops/models.py index c9c9dd2f..b6749984 100644 --- a/fahari/ops/models.py +++ b/fahari/ops/models.py @@ -134,6 +134,10 @@ def facility_system_name(self): + f"Version: {self.facility_system.version}" ) + @property + def is_resolved(self): + return self.resolved is not None + def get_absolute_url(self): update_url = reverse_lazy("ops:ticket_update", kwargs={"pk": self.pk}) return update_url diff --git a/fahari/ops/tests/test_models.py b/fahari/ops/tests/test_models.py index 73c428d4..4f811e96 100644 --- a/fahari/ops/tests/test_models.py +++ b/fahari/ops/tests/test_models.py @@ -63,6 +63,27 @@ def test_facility_system_ticket_str(): ) +def test_facility_system_ticket_is_resolved(): + org = baker.make(Organisation) + facility = baker.make(Facility, organisation=org, name="Test") + system = baker.make(System, organisation=org, name="System") + vrs = "0.0.1" + facility_system = baker.make( + FacilitySystem, + facility=facility, + system=system, + version=vrs, + ) + facility_system_details = baker.make( + FacilitySystemTicket, + facility_system=facility_system, + details="Details", + resolved=timezone.now(), + resolved_by="User", + ) + assert facility_system_details.is_resolved is True + + def test_facility_ticket_status(staff_user): open_ticket = baker.make(FacilitySystemTicket, resolved=None, resolved_by=None) assert open_ticket.is_open is True diff --git a/fahari/ops/tests/test_views.py b/fahari/ops/tests/test_views.py index 31b825b2..feffae6f 100644 --- a/fahari/ops/tests/test_views.py +++ b/fahari/ops/tests/test_views.py @@ -1,8 +1,17 @@ +import uuid + import pytest from django.urls import reverse +from model_bakery import baker from rest_framework import status -from fahari.ops.views import FacilitySystemsView, FacilitySystemTicketsView +from fahari.ops.models import FacilitySystemTicket, TimeSheet +from fahari.ops.views import ( + FacilitySystemsView, + FacilitySystemTicketResolveView, + FacilitySystemTicketsView, + TimeSheetApproveView, +) pytestmark = pytest.mark.django_db @@ -75,3 +84,41 @@ def test_tickets_context_data(): ctx = v.get_context_data() assert ctx["active"] == "facilities-nav" assert ctx["selected"] == "tickets" + + +def test_timesheet_approve_view_happy_case(request_with_user): + assert request_with_user.user is not None + timesheet = baker.make(TimeSheet, approved_by=None, approved_at=None) + view = TimeSheetApproveView() + resp = view.post(request_with_user, pk=timesheet.pk) + assert resp.status_code == 302 + + timesheet.refresh_from_db() + assert timesheet.approved_by is not None + assert timesheet.approved_at is not None + + +def test_timesheet_approve_view_error_case(request_with_user): + fake_pk = uuid.uuid4() + view = TimeSheetApproveView() + resp = view.post(request_with_user, pk=fake_pk) + assert resp.status_code == 200 # page re-rendered with an error + + +def test_ticket_resolve_view_happy_case(request_with_user): + assert request_with_user.user is not None + open_ticket = baker.make(FacilitySystemTicket, resolved=None, resolved_by=None) + view = FacilitySystemTicketResolveView() + resp = view.post(request_with_user, pk=open_ticket.pk) + assert resp.status_code == 302 + + open_ticket.refresh_from_db() + assert open_ticket.resolved is not None + assert open_ticket.resolved_by is not None + + +def test_ticket_resolve_view_error_case(request_with_user): + fake_pk = uuid.uuid4() + view = FacilitySystemTicketResolveView() + resp = view.post(request_with_user, pk=fake_pk) + assert resp.status_code == 200 # page re-rendered with an error diff --git a/fahari/ops/urls.py b/fahari/ops/urls.py index c7394243..01730aa9 100644 --- a/fahari/ops/urls.py +++ b/fahari/ops/urls.py @@ -14,6 +14,7 @@ FacilitySystemsView, FacilitySystemTicketCreateView, FacilitySystemTicketDeleteView, + FacilitySystemTicketResolveView, FacilitySystemTicketsView, FacilitySystemTicketUpdateView, FacilitySystemUpdateView, @@ -25,6 +26,7 @@ StockReceiptVerificationDeleteView, StockReceiptVerificationUpdateView, StockReceiptVerificationView, + TimeSheetApproveView, TimeSheetCreateView, TimeSheetDeleteView, TimeSheetsView, @@ -69,6 +71,11 @@ view=FacilitySystemTicketDeleteView.as_view(), name="ticket_delete", ), + path( + "ticket_resolve/", + view=FacilitySystemTicketResolveView.as_view(), + name="ticket_resolve", + ), path("activity_logs", view=ActivityLogView.as_view(), name="activity_logs"), path( "activity_log_create", @@ -157,6 +164,11 @@ view=TimeSheetDeleteView.as_view(), name="timesheet_delete", ), + path( + "timesheet_approve/", + view=TimeSheetApproveView.as_view(), + name="timesheet_approve", + ), path( "stock_receipt_verifications", view=StockReceiptVerificationView.as_view(), diff --git a/fahari/ops/views.py b/fahari/ops/views.py index 09c14cb0..b2b0ebb0 100644 --- a/fahari/ops/views.py +++ b/fahari/ops/views.py @@ -1,5 +1,9 @@ from django.contrib.auth.mixins import LoginRequiredMixin +from django.core.exceptions import ValidationError +from django.http import HttpResponseRedirect +from django.shortcuts import render from django.urls import reverse_lazy +from django.utils import timezone from django.views.generic import CreateView, DeleteView, TemplateView, UpdateView from fahari.common.views import ApprovedMixin, BaseFormMixin, BaseView @@ -130,6 +134,25 @@ class FacilitySystemTicketDeleteView(FacilitySystemTicketContextMixin, DeleteVie success_url = reverse_lazy("ops:tickets") +class FacilitySystemTicketResolveView( + FacilitySystemTicketContextMixin, + TemplateView, +): + template_name = "ops/ticket_resolve.html" + success_url = reverse_lazy("ops:tickets") + + def post(self, request, *args, **kwargs): + try: + pk = kwargs["pk"] + ticket = FacilitySystemTicket.objects.get(pk=pk) + ticket.resolved_by = str(request.user) + ticket.resolved = timezone.now() + ticket.save() + return HttpResponseRedirect(self.success_url) + except (FacilitySystemTicket.DoesNotExist, ValidationError, KeyError) as e: + return render(request, self.template_name, {"errors": [e]}) + + class FacilitySystemTicketViewSet(BaseView): queryset = FacilitySystemTicket.objects.filter( active=True, @@ -381,6 +404,22 @@ class TimeSheetDeleteView(TimeSheetContextMixin, DeleteView, BaseFormMixin): success_url = reverse_lazy("ops:timesheets") +class TimeSheetApproveView(TimeSheetContextMixin, TemplateView): + template_name = "ops/timesheet_approve.html" + success_url = reverse_lazy("ops:timesheets") + + def post(self, request, *args, **kwargs): + try: + pk = kwargs["pk"] + timesheet = TimeSheet.objects.get(pk=pk) + timesheet.approved_by = request.user + timesheet.approved_at = timezone.now() + timesheet.save() + return HttpResponseRedirect(self.success_url) + except (TimeSheet.DoesNotExist, ValidationError, KeyError) as e: + return render(request, self.template_name, {"errors": [e]}) + + class TimeSheetViewSet(BaseView): queryset = TimeSheet.objects.filter( active=True, diff --git a/fahari/static/admin/favicon.ico b/fahari/static/admin/favicon.ico new file mode 100644 index 00000000..fe1e643d Binary files /dev/null and b/fahari/static/admin/favicon.ico differ diff --git a/fahari/templates/ops/facilitysystemticket_form.html b/fahari/templates/ops/facilitysystemticket_form.html index 40760e0e..15646026 100644 --- a/fahari/templates/ops/facilitysystemticket_form.html +++ b/fahari/templates/ops/facilitysystemticket_form.html @@ -21,6 +21,15 @@ Delete Ticket {% endif %} + + {% if perms.ops.can_resolve_ticket and not object.is_resolved %} + + + + + Resolve Ticket + + {% endif %} {% endif %} diff --git a/fahari/templates/ops/ticket_resolve.html b/fahari/templates/ops/ticket_resolve.html new file mode 100644 index 00000000..b1df1da9 --- /dev/null +++ b/fahari/templates/ops/ticket_resolve.html @@ -0,0 +1,42 @@ + +{% extends "base.html" %} +{% load crispy_forms_tags %} +{% block title %} + Resolve Ticket +{% endblock title %} +{% block content %} +← Back +{% if errors %} +
+
    + {% for error in errors %} +
  • {{error}}
  • + {% endfor %} +
+
+{% endif %} +
+
+
+
+
+
+
+
+
+
+

Confirm Resolve

+
+
+ {% csrf_token %} +

Are you sure you want to resolve this ticket?

+ +
+
+
+
+
+
+
+
+{% endblock content %} diff --git a/fahari/templates/ops/timesheet_approve.html b/fahari/templates/ops/timesheet_approve.html new file mode 100644 index 00000000..6c40db23 --- /dev/null +++ b/fahari/templates/ops/timesheet_approve.html @@ -0,0 +1,42 @@ + +{% extends "base.html" %} +{% load crispy_forms_tags %} +{% block title %} + Approve Time Sheet +{% endblock title %} +{% block content %} +← Back +{% if errors %} +
+
    + {% for error in errors %} +
  • {{error}}
  • + {% endfor %} +
+
+{% endif %} +
+
+
+
+
+
+
+
+
+
+

Confirm Approval

+
+
+ {% csrf_token %} +

Are you sure you want to approve this timesheet?

+ +
+
+
+
+
+
+
+
+{% endblock content %} diff --git a/fahari/templates/ops/timesheet_form.html b/fahari/templates/ops/timesheet_form.html index a30725e0..7fc698ad 100644 --- a/fahari/templates/ops/timesheet_form.html +++ b/fahari/templates/ops/timesheet_form.html @@ -21,6 +21,15 @@ Delete Time Sheet {% endif %} + + {% if perms.ops.can_approve_timesheet and not object.is_approved %} + + + + + Approve Time Sheet + + {% endif %} {% endif %}