133 lines
4.5 KiB
Python
133 lines
4.5 KiB
Python
from django.http import Http404
|
|
from django.utils.cache import patch_vary_headers
|
|
from rest_framework import generics, permissions, status
|
|
from rest_framework.exceptions import APIException
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
|
|
from finance.enums import PayrollRunStatus
|
|
from finance.models import PayrollAuditEvent, PayrollStatement
|
|
from finance.payroll.exceptions import PayrollOperationError
|
|
from finance.payroll.services import acknowledge_statement
|
|
|
|
from .serializers import (
|
|
OwnPayrollStatementSerializer,
|
|
OwnPayrollStatementSummarySerializer,
|
|
PayrollStatementAcknowledgementSerializer,
|
|
)
|
|
|
|
|
|
class PayrollConflict(APIException):
|
|
status_code = status.HTTP_409_CONFLICT
|
|
default_code = "payroll_conflict"
|
|
|
|
|
|
class NoStorePayrollResponseMixin:
|
|
def finalize_response(self, request, response, *args, **kwargs):
|
|
response = super().finalize_response(request, response, *args, **kwargs)
|
|
response["Cache-Control"] = "private, no-store, max-age=0"
|
|
response["Pragma"] = "no-cache"
|
|
patch_vary_headers(response, ("Authorization", "Cookie"))
|
|
return response
|
|
|
|
|
|
def own_published_statements(user):
|
|
return (
|
|
PayrollStatement.objects.filter(
|
|
employee=user,
|
|
run__status=PayrollRunStatus.PUBLISHED,
|
|
)
|
|
.select_related("run", "run__period")
|
|
.prefetch_related("lines", "acknowledgement")
|
|
.order_by("-run__period__date_from", "-run__revision")
|
|
)
|
|
|
|
|
|
class OwnPayrollStatementListView(
|
|
NoStorePayrollResponseMixin,
|
|
generics.ListAPIView,
|
|
):
|
|
permission_classes = [permissions.IsAuthenticated]
|
|
serializer_class = OwnPayrollStatementSummarySerializer
|
|
|
|
def get_queryset(self):
|
|
return own_published_statements(self.request.user)
|
|
|
|
def list(self, request, *args, **kwargs):
|
|
queryset = self.filter_queryset(self.get_queryset())
|
|
page = self.paginate_queryset(queryset)
|
|
statements = list(page) if page is not None else list(queryset)
|
|
PayrollAuditEvent.objects.bulk_create(
|
|
[
|
|
PayrollAuditEvent(
|
|
actor=request.user,
|
|
action="payroll.statement_summary_viewed",
|
|
object_type=statement._meta.label_lower,
|
|
object_id=str(statement.pk),
|
|
period=statement.run.period,
|
|
after_hash=statement.result_hash,
|
|
metadata={"channel": "employee_api"},
|
|
)
|
|
for statement in statements
|
|
]
|
|
)
|
|
serializer = self.get_serializer(statements, many=True)
|
|
if page is not None:
|
|
return self.get_paginated_response(serializer.data)
|
|
return Response(serializer.data)
|
|
|
|
|
|
class OwnPayrollStatementDetailView(
|
|
NoStorePayrollResponseMixin,
|
|
generics.RetrieveAPIView,
|
|
):
|
|
permission_classes = [permissions.IsAuthenticated]
|
|
serializer_class = OwnPayrollStatementSerializer
|
|
lookup_field = "public_id"
|
|
lookup_url_kwarg = "public_id"
|
|
|
|
def get_queryset(self):
|
|
return own_published_statements(self.request.user)
|
|
|
|
def retrieve(self, request, *args, **kwargs):
|
|
statement = self.get_object()
|
|
PayrollAuditEvent.objects.create(
|
|
actor=request.user,
|
|
action="payroll.statement_viewed",
|
|
object_type=statement._meta.label_lower,
|
|
object_id=str(statement.pk),
|
|
period=statement.run.period,
|
|
after_hash=statement.result_hash,
|
|
metadata={"channel": "employee_api"},
|
|
)
|
|
return Response(self.get_serializer(statement).data)
|
|
|
|
|
|
class OwnPayrollStatementAcknowledgeView(
|
|
NoStorePayrollResponseMixin,
|
|
APIView,
|
|
):
|
|
permission_classes = [permissions.IsAuthenticated]
|
|
|
|
def post(self, request, public_id):
|
|
statement = (
|
|
own_published_statements(request.user).filter(public_id=public_id).first()
|
|
)
|
|
if statement is None:
|
|
raise Http404
|
|
try:
|
|
acknowledgement = acknowledge_statement(
|
|
statement.pk,
|
|
actor=request.user,
|
|
)
|
|
except PayrollOperationError as exc:
|
|
if exc.code == "STATEMENT_NOT_FOUND":
|
|
raise Http404 from exc
|
|
raise PayrollConflict(
|
|
detail={"code": exc.code, "message": exc.message}
|
|
) from exc
|
|
return Response(
|
|
PayrollStatementAcknowledgementSerializer(acknowledgement).data,
|
|
status=status.HTTP_200_OK,
|
|
)
|