Add timesheet report generation API route

This commit is contained in:
Tanner Collin 2017-06-28 06:24:45 +00:00
parent 9817a4ebba
commit 4469db2602
4 changed files with 150 additions and 31 deletions

View File

@ -17,14 +17,14 @@ class Client(models.Model):
business_number = models.CharField(max_length=16)
def __str__(self):
return 'Client: ' + self.user.username
return self.user.first_name + ' ' + self.user.last_name
class Provider(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
sin = models.CharField(max_length=16)
def __str__(self):
return 'Provider: ' + self.user.username
return self.user.first_name + ' ' + self.user.last_name
class WorkType(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False)
@ -63,7 +63,6 @@ class Shift(models.Model):
set_end = models.DateTimeField()
actual_start = models.DateTimeField(null=True)
actual_end = models.DateTimeField(null=True)
amount = models.DecimalField(max_digits=8, decimal_places=2, null=True)
description = models.CharField(max_length=100, null=True)
chart = models.TextField(max_length=1000, null=True)
deleted = models.BooleanField(default=False)

View File

@ -42,19 +42,19 @@ class PriceSerializer(serializers.ModelSerializer):
raise serializers.ValidationError("User is not a client.")
client = user.client
manage_objs = Manage.objects.filter(client=client).filter(deleted=False)
manage_objs = Manage.objects.filter(deleted=False).filter(client=client)
employee_uuid = validated_data.pop('get_employee_uuid')
employee = get_object_or_404(manage_objs, uuid=employee_uuid)
worktype_objs = WorkType.objects.filter(client=client).filter(deleted=False)
worktype_objs = WorkType.objects.filter(deleted=False).filter(client=client)
work_type_uuid = validated_data.pop('get_work_type_uuid')
work_type = get_object_or_404(worktype_objs, uuid=work_type_uuid)
if not employee.approved:
raise serializers.ValidationError("Employee hasn't approved employment yet.")
if Price.objects.filter(management=employee) \
if Price.objects.filter(deleted=False) \
.filter(work_type=work_type) \
.filter(deleted=False).exists():
.filter(management=employee).exists():
raise serializers.ValidationError("Price for this work already defined.")
validated_data['management'] = employee
@ -96,9 +96,9 @@ class EmployeeSerializer(serializers.ModelSerializer):
provider_email = validated_data.pop('provider_email')
provider = get_object_or_404(Provider, user__email=provider_email)
if Manage.objects.filter(client=client) \
if Manage.objects.filter(deleted=False) \
.filter(provider=provider) \
.filter(deleted=False).exists():
.filter(client=client).exists():
raise serializers.ValidationError("Employee already added.")
validated_data['client'] = client
@ -172,13 +172,27 @@ class UserSerializer(serializers.ModelSerializer):
fields = ('username', 'is_active', 'first_name', 'last_name', 'email', 'userinfo', 'client', 'provider')
depth = 1
class CShiftSerializer(serializers.ModelSerializer):
class ShiftSerializer(serializers.ModelSerializer):
price = serializers.SerializerMethodField()
amount = serializers.SerializerMethodField()
def get_price(self, obj):
return obj.price.uuid
def get_amount(self, obj):
if obj.actual_end:
shift_length = obj.actual_end - obj.actual_start
return Decimal(shift_length.total_seconds()) / 3600 * obj.set_price
else:
return None
class CShiftSerializer(ShiftSerializer):
get_price_uuid = serializers.UUIDField(write_only=True)
class Meta:
model = Shift
fields = ('uuid', 'chart', 'get_price_uuid', 'price', 'set_price', 'set_date', 'set_start', 'set_end', 'actual_start', 'actual_end', 'amount', 'description', 'deleted')
read_only_fields = ('chart', 'price', 'set_price', 'set_date', 'actual_start', 'actual_end', 'amount', 'deleted')
read_only_fields = ('chart', 'price', 'set_price', 'set_date', 'actual_start', 'actual_end', 'deleted')
def create(self, validated_data):
user = self.context['request'].user
@ -187,7 +201,7 @@ class CShiftSerializer(serializers.ModelSerializer):
raise serializers.ValidationError("User is not a client.")
client = user.client
price_objs = Price.objects.filter(management__client=client).filter(deleted=False)
price_objs = Price.objects.filter(deleted=False).filter(management__client=client)
price_uuid = validated_data.pop('get_price_uuid')
price = get_object_or_404(price_objs, uuid=price_uuid)
@ -215,13 +229,13 @@ class CShiftSerializer(serializers.ModelSerializer):
validated_data['price'] = price
return serializers.ModelSerializer.create(self, validated_data)
class PShiftSerializer(serializers.ModelSerializer):
class PShiftSerializer(ShiftSerializer):
action = serializers.ChoiceField(write_only=True, choices=['checkin', 'checkout'])
class Meta:
model = Shift
fields = ('uuid', 'action', 'chart', 'price', 'set_price', 'set_date', 'set_start', 'set_end', 'actual_start', 'actual_end', 'amount', 'description', 'deleted')
read_only_fields = ('uuid', 'price', 'set_price', 'set_date', 'set_start', 'set_end', 'actual_start', 'actual_end', 'amount', 'description', 'deleted')
read_only_fields = ('uuid', 'price', 'set_price', 'set_date', 'set_start', 'set_end', 'actual_start', 'actual_end', 'description', 'deleted')
def update(self, instance, validated_data):
action = validated_data['action']
@ -247,9 +261,24 @@ class PShiftSerializer(serializers.ModelSerializer):
raise serializers.ValidationError("No chart info provided.")
instance.actual_end = datetime.now(timezone.utc)
shift_length = instance.actual_end - instance.actual_start
instance.amount = Decimal(shift_length.total_seconds()) / 3600 * instance.set_price
instance.chart = chart
instance.save()
return instance
class TimeSheetSerializer(serializers.Serializer):
class ShiftSerializer(serializers.Serializer):
date = serializers.DateField()
start = serializers.DateTimeField()
end = serializers.DateTimeField()
hours = serializers.DecimalField(max_digits=8, decimal_places=2)
rate = serializers.DecimalField(max_digits=8, decimal_places=2)
amount = serializers.DecimalField(max_digits=8, decimal_places=2)
work = serializers.CharField()
client = serializers.CharField()
provider = serializers.CharField()
payday = serializers.DateField()
paystart = serializers.DateField()
payend = serializers.DateField()
shifts = ShiftSerializer(many=True)

View File

@ -1,11 +1,18 @@
import calendar, datetime
from datetime import timedelta
from decimal import Decimal
from itertools import groupby
from django.shortcuts import render
from django.contrib.auth.models import User, Group
from django.core import exceptions
from django.db.models import Q
from caremyway.api.models import UserInfo, Client, Provider, WorkType, Manage, Price, Shift
from rest_framework import viewsets, permissions, status
from rest_framework import viewsets, permissions, status, serializers
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.views import APIView
from caremyway.api.serializers import UserSerializer, UserInfoSerializer, ClientSerializer, ProviderSerializer, WorkTypeSerializer, EmployeeSerializer, EmployerSerializer, PriceSerializer, CShiftSerializer, PShiftSerializer
from rest_framework.renderers import StaticHTMLRenderer
from caremyway.api.serializers import UserSerializer, UserInfoSerializer, ClientSerializer, ProviderSerializer, WorkTypeSerializer, EmployeeSerializer, EmployerSerializer, PriceSerializer, CShiftSerializer, PShiftSerializer, TimeSheetSerializer
class UserViewSet(viewsets.ModelViewSet):
lookup_field = 'username'
@ -18,7 +25,7 @@ class UserViewSet(viewsets.ModelViewSet):
return User.objects.filter(username=self.request.user)
class UserInfoViewSet(viewsets.ModelViewSet):
lookup_field = "user__username"
lookup_field = 'user__username'
serializer_class = UserInfoSerializer
# Disallow DELETE
@ -28,7 +35,7 @@ class UserInfoViewSet(viewsets.ModelViewSet):
return UserInfo.objects.filter(user__username=self.request.user)
class ClientViewSet(viewsets.ModelViewSet):
lookup_field = "user__username"
lookup_field = 'user__username'
serializer_class = ClientSerializer
# Disallow DELETE
@ -38,7 +45,7 @@ class ClientViewSet(viewsets.ModelViewSet):
return Client.objects.filter(user__username=self.request.user)
class ProviderViewSet(viewsets.ModelViewSet):
lookup_field = "user__username"
lookup_field = 'user__username'
serializer_class = ProviderSerializer
# Disallow DELETE
@ -48,7 +55,7 @@ class ProviderViewSet(viewsets.ModelViewSet):
return Provider.objects.filter(user__username=self.request.user)
class WorkTypeViewSet(viewsets.ModelViewSet):
lookup_field = "uuid"
lookup_field = 'uuid'
serializer_class = WorkTypeSerializer
def get_queryset(self):
@ -61,7 +68,7 @@ class WorkTypeViewSet(viewsets.ModelViewSet):
return Response(status=status.HTTP_204_NO_CONTENT)
class EmployeeViewSet(viewsets.ModelViewSet):
lookup_field = "uuid"
lookup_field = 'uuid'
serializer_class = EmployeeSerializer
def get_queryset(self):
@ -74,7 +81,7 @@ class EmployeeViewSet(viewsets.ModelViewSet):
return Response(status=status.HTTP_204_NO_CONTENT)
class EmployerViewSet(viewsets.ModelViewSet):
lookup_field = "uuid"
lookup_field = 'uuid'
serializer_class = EmployerSerializer
# Disallow creation and deletions of relationships
@ -84,7 +91,7 @@ class EmployerViewSet(viewsets.ModelViewSet):
return Manage.objects.filter(provider__user__username=self.request.user)
class PriceViewSet(viewsets.ModelViewSet):
lookup_field = "uuid"
lookup_field = 'uuid'
serializer_class = PriceSerializer
def get_queryset(self):
@ -97,12 +104,12 @@ class PriceViewSet(viewsets.ModelViewSet):
return Response(status=status.HTTP_204_NO_CONTENT)
class CShiftViewSet(viewsets.ModelViewSet):
lookup_field = "uuid"
lookup_field = 'uuid'
serializer_class = CShiftSerializer
def get_queryset(self):
return Shift.objects.filter(price__management__client__user__username=self.request.user) \
.filter(deleted=False) \
return Shift.objects.filter(deleted=False) \
.filter(price__management__client__user__username=self.request.user) \
.order_by('-set_start')
def destroy(self, request, *args, **kwargs):
@ -116,17 +123,100 @@ class CShiftViewSet(viewsets.ModelViewSet):
return Response(status=status.HTTP_204_NO_CONTENT)
class PShiftViewSet(viewsets.ModelViewSet):
lookup_field = "uuid"
lookup_field = 'uuid'
serializer_class = PShiftSerializer
# Disallow creation and deletions of relationships
http_method_names = ['get', 'head', 'put', 'options']
def get_queryset(self):
return Shift.objects.filter(price__management__provider__user__username=self.request.user) \
.filter(deleted=False) \
return Shift.objects.filter(deleted=False) \
.filter(price__management__provider__user__username=self.request.user) \
.order_by('-set_start')
def validate_param(value, field):
class ValidateParam(serializers.Serializer):
value = field
obj = ValidateParam(data={'value': value})
if obj.is_valid():
return obj.validated_data.get('value');
else:
raise serializers.ValidationError(obj.errors['value'])
def get_paystart(payday):
# Assumes payday is a valided date obj
if payday.day == 15:
paystart_day = 1
elif payday.day == calendar.monthrange(payday.year, payday.month)[1]:
paystart_day = 16
else:
raise serializers.ValidationError("Date is not a valid payday.")
return datetime.date(payday.year, payday.month, paystart_day) - timedelta(days=4)
def get_payend(payday):
return payday - timedelta(days=4)
def get_timesheet(user, payday, manage=None):
paystart = get_paystart(payday)
payend = get_payend(payday)
shifts = Shift.objects.filter(deleted=False) \
.exclude(actual_end__isnull=True) \
.filter(
Q(price__management__client__user__username=user)
| Q(price__management__provider__user__username=user)) \
.filter(set_date__gte=paystart) \
.filter(set_date__lte=payend) \
.order_by('price__management', 'actual_start')
if manage:
shifts = shifts.filter(price__management__uuid=manage)
manage_shifts = groupby(shifts, lambda x: x.price.management)
timesheets = []
for management, shifts in manage_shifts:
timesheet = {}
timesheet['client'] = management.client
timesheet['provider'] = management.provider
timesheet['payday'] = payday
timesheet['paystart'] = paystart
timesheet['payend'] = payend
timesheet['shifts'] = []
for s in shifts:
shift = {}
shift['date'] = s.set_date
shift['start'] = s.actual_start
shift['end'] = s.actual_end
shift['hours'] = (shift['end'] - shift['start']).total_seconds() / 3600
shift['rate'] = s.set_price
shift['amount'] = Decimal(shift['hours']) * shift['rate']
shift['work'] = s.price.work_type.label
timesheet['shifts'].append(shift)
timesheets.append(timesheet)
return timesheets
class ReportView(APIView):
def get(self, request, *args, **kwargs):
response = []
report_type = kwargs['type']
qp = request.query_params
if report_type == 'timesheet':
payday = validate_param(qp.get('payday'), serializers.DateField())
manage = validate_param(qp.get('manage'), serializers.UUIDField(allow_null=True))
timesheet = get_timesheet(request.user, payday, manage)
response = TimeSheetSerializer(timesheet, many=True).data
return Response(response, status=status.HTTP_200_OK)
@api_view()
def null_view(request):
return Response(status=status.HTTP_400_BAD_REQUEST)

View File

@ -34,6 +34,7 @@ router.register(r'pshift', views.PShiftViewSet, 'pshift')
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^report/(?P<type>.+)/$', views.ReportView.as_view()),
url(r'^admin/', include(admin.site.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^rest-auth/registration/account-email-verification-sent/', views.null_view, name='account_email_verification_sent'),