diff --git a/horilla/__init__.py b/horilla/__init__.py index 4e9da84b4..3ed72357c 100755 --- a/horilla/__init__.py +++ b/horilla/__init__.py @@ -8,4 +8,5 @@ from horilla import ( horilla_context_processors, horilla_middlewares, horilla_settings, + rest_conf, ) diff --git a/horilla/rest_conf.py b/horilla/rest_conf.py new file mode 100644 index 000000000..a9c08e10b --- /dev/null +++ b/horilla/rest_conf.py @@ -0,0 +1,49 @@ +""" +rest_conf.py +""" +from horilla import settings +from horilla.settings import INSTALLED_APPS +from datetime import timedelta + + +# Injecting installed apps to settings + +REST_APPS = ["rest_framework", + "rest_framework_simplejwt", + "drf_yasg", + "horilla_api" + ] + +INSTALLED_APPS.extend(REST_APPS) + +REST_FRAMEWORK_SETTINGS = { + 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'], + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ), + 'PAGE_SIZE': 20, +} + +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(days=30), +} +SWAGGER_SETTINGS = { + 'SECURITY_DEFINITIONS': { + 'Bearer': { + 'type': 'apiKey', + 'name': 'Authorization', + 'in': 'header', + 'description': 'Enter your Bearer token here', + }, + 'Basic': { + 'type': 'basic', + 'description': 'Basic authentication. Enter your username and password.', + } + }, + 'SECURITY': [{'Bearer': []}, {'Basic': []}], +} +# Inject the REST framework settings into the Django project settings +setattr(settings, 'REST_FRAMEWORK', REST_FRAMEWORK_SETTINGS) +setattr(settings, 'SIMPLE_JWT', SIMPLE_JWT) +setattr(settings, 'SWAGGER_SETTINGS', SWAGGER_SETTINGS) diff --git a/horilla/urls.py b/horilla/urls.py index 68a1e15a5..4c4ac617c 100755 --- a/horilla/urls.py +++ b/horilla/urls.py @@ -35,6 +35,7 @@ urlpatterns = [ "^inbox/notifications/", include(notifications.urls, namespace="notifications") ), path("i18n/", include("django.conf.urls.i18n")), + path("api/", include("horilla_api.urls")), ] if settings.DEBUG: diff --git a/horilla_api/__init__.py b/horilla_api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/horilla_api/admin.py b/horilla_api/admin.py new file mode 100644 index 000000000..8c38f3f3d --- /dev/null +++ b/horilla_api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/horilla_api/api_decorators/base/decorators.py b/horilla_api/api_decorators/base/decorators.py new file mode 100644 index 000000000..9b78d96b7 --- /dev/null +++ b/horilla_api/api_decorators/base/decorators.py @@ -0,0 +1,101 @@ +from rest_framework.permissions import BasePermission +from base.models import MultipleApprovalManagers +from employee.models import EmployeeWorkInformation +from functools import wraps +from rest_framework.response import Response +from rest_framework import status + + +class ManagerPermission(BasePermission): + leave_perm = [ + "leave.view_leaverequest", + "leave.change_leaverequest", + "leave.delete_leaverequest", + ] + + def has_permission(self, request, perm): + user = request.user + employee = user.employee_get + if perm in self.leave_perm: + is_approval_manager = MultipleApprovalManagers.objects.filter( + employee_id=employee.id + ).exists() + if is_approval_manager: + return True + + is_manager = EmployeeWorkInformation.objects.filter( + reporting_manager_id=employee + ).exists() + + if user.has_perm(perm) or is_manager: + return True + return False + + +def manager_permission_required(perm): + """ + Decorator for views that checks whether the user has appropriate manager permissions. + """ + def decorator(func): + @wraps(func) + def wrapper(self, request, *args, **kwargs): + permission = ManagerPermission() + if permission.has_permission(request, perm): + return func(self, request, *args, **kwargs) + else: + return Response( + {"error": "You do not have permission to perform this action."}, + status=status.HTTP_403_FORBIDDEN, + ) + return wrapper + return decorator + + +def manager_or_owner_permission_required(model_class, perm): + """ + Decorator for views that checks whether the user has either manager or owner permissions and a specific permission for a specific object for a given model class. + """ + def decorator(func): + @wraps(func) + def wrapper(self, request, pk=None, *args, **kwargs): + if pk: + try: + obj = model_class.objects.get(pk=pk) + # Check if the requesting user is the owner of the object + if obj.employee_id == request.user.employee_get: + return func(self, request, pk, *args, **kwargs) + except model_class.DoesNotExist: + return Response({"error": f"{model_class.__name__} does not exist"}, status=status.HTTP_404_NOT_FOUND) + else: + if request.data.get('employee_id', None) == request.user.employee_get.id: + return func(self, request, *args, **kwargs) + # If not the owner, check for manager permission + permission = ManagerPermission() + if permission.has_permission(request, perm) and pk: + return func(self, request,pk, *args, **kwargs) + elif permission.has_permission(request, perm) and pk == None: + return func(self, request, *args, **kwargs) + else: + return Response( + {"error": "You do not have permission to perform this action."}, + status=status.HTTP_403_FORBIDDEN, + ) + + return wrapper + + return decorator + + +def check_approval_status(model, perm): + """ checking the object approval status """ + def decorator(func): + @wraps(func) + def wrapper(self, request, pk, *args, **kwargs): + object = model.objects.filter(id = pk).first() + if object.approved: + return Response({"error":f"Approved {model.__name__} can't preform this action "},status=400) + if object.canceled: + return Response({"error":f"Canceled {model.__name__} can't preform this action "},status=400) + return func(self, request, pk ,*args, **kwargs) + return wrapper + return decorator diff --git a/horilla_api/api_decorators/employee/decorators.py b/horilla_api/api_decorators/employee/decorators.py new file mode 100644 index 000000000..21b366804 --- /dev/null +++ b/horilla_api/api_decorators/employee/decorators.py @@ -0,0 +1,19 @@ +from functools import wraps +from django.http import HttpResponseForbidden +from django.utils.decorators import method_decorator + +def or_condition(*decorators): + """ + Combines multiple decorators with OR logic. + """ + def decorator(view_func): + @wraps(view_func) + def _wrapped_view(request, *args, **kwargs): + # Check if any of the decorators allow access + for dec in decorators: + if dec(view_func)(request, *args, **kwargs): + return view_func(request, *args, **kwargs) + # If none of the decorators allow access, return forbidden response + return HttpResponseForbidden("You don't have permission to access this page.") + return _wrapped_view + return decorator diff --git a/horilla_api/api_filters/asset/filters.py b/horilla_api/api_filters/asset/filters.py new file mode 100644 index 000000000..89d3a2e42 --- /dev/null +++ b/horilla_api/api_filters/asset/filters.py @@ -0,0 +1,11 @@ +from django_filters import FilterSet +import django_filters +from asset.models import * + +class AssetCategoryFilter(FilterSet): + + search = django_filters.CharFilter(field_name='asset_category_name', lookup_expr="icontains") + + class Meta: + model = AssetCategory + fields = "__all__" \ No newline at end of file diff --git a/horilla_api/api_methods/base/methods.py b/horilla_api/api_methods/base/methods.py new file mode 100644 index 000000000..29c5a602a --- /dev/null +++ b/horilla_api/api_methods/base/methods.py @@ -0,0 +1,66 @@ +from django.http import QueryDict +from rest_framework.pagination import PageNumberPagination +from employee.models import EmployeeWorkInformation +from collections import Counter +from django.db.models import Q + + +def get_filter_url(current_url, request): + url_parts = current_url.split('?') + base_url = request.path + query_params = QueryDict(url_parts[1], mutable=True) + query_params.pop('groupby_field', None) + return base_url + '?' + query_params.urlencode() + + +def groupby_queryset(request, url, field_name, queryset): + queryset_with_counts = queryset.values( + field_name) + + counts = Counter(item[field_name] for item in queryset_with_counts) + + result_list = [] + for i in counts: + result_list.append({field_name: i, 'count': counts[i]}) + + counts_and_objects = [] + url = get_filter_url(url, request) + for item in result_list: + count = item['count'] + related_fields = field_name.split("__") + if item[field_name]: + related_obj = queryset.filter( + **{field_name: item[field_name]}).first() + for field in related_fields: + related_obj = getattr(related_obj, field) + counts_and_objects.append( + {'count': count, + 'name': str(related_obj), + "filter_url": f"{url}&{field_name}={item[field_name]}"}) + pagination = PageNumberPagination() + page = pagination.paginate_queryset(counts_and_objects, request) + return pagination.get_paginated_response(page) + + +def permission_based_queryset(user, perm, queryset, user_obj=None): + if user.has_perm(perm): + return queryset + + employee = user.employee_get + is_manager = EmployeeWorkInformation.objects.filter( + reporting_manager_id=employee + ).exists() + if is_manager: + if user_obj: + return queryset.filter( + Q(employee_id=employee) | + Q(employee_id__employee_work_info__reporting_manager_id=employee) + ) + manager_filter = Q(employee_id=employee) + subordinates_filter = Q( + employee_id__employee_work_info__reporting_manager_id=employee) + merged_filter = manager_filter | subordinates_filter + merged_queryset = queryset.filter(merged_filter) + return merged_queryset + + return queryset.filter(employee_id=employee) diff --git a/horilla_api/api_methods/employee/methods.py b/horilla_api/api_methods/employee/methods.py new file mode 100644 index 000000000..c121291c4 --- /dev/null +++ b/horilla_api/api_methods/employee/methods.py @@ -0,0 +1,31 @@ +from django.http import QueryDict +from employee.models import Employee +from rest_framework.pagination import PageNumberPagination +from base.models import * +from employee.models import * + + +def get_next_badge_id(): + """ + This method is used to generate badge id + """ + try: + highest_badge_id = Employee.objects.filter( + badge_id__isnull=False).order_by('-badge_id').first().badge_id + except AttributeError: + highest_badge_id = None + + # Increment the badge_id if it exists, otherwise start from '1' + if highest_badge_id: + if '#' in highest_badge_id: + prefix, number = highest_badge_id.split( + '#') # Split prefix and number + # Increment the number + new_number = str(int(number) + 1).zfill(len(number)) + new_badge_id = f"{prefix}#{new_number}" + else: + # Add number to existing prefix + new_badge_id = f"{highest_badge_id}#001" + else: + new_badge_id = "EMP#001" # Default start badge ID if no employees exist + return new_badge_id diff --git a/horilla_api/api_serializers/asset/serializers.py b/horilla_api/api_serializers/asset/serializers.py new file mode 100644 index 000000000..63c4e0b39 --- /dev/null +++ b/horilla_api/api_serializers/asset/serializers.py @@ -0,0 +1,137 @@ +from rest_framework import serializers +from asset.models import * + + +class AssetCategorySerializer(serializers.ModelSerializer): + asset_count = serializers.SerializerMethodField() + class Meta: + model = AssetCategory + exclude = ['created_at', 'created_by', 'company_id', 'is_active'] + + def get_asset_count(self, obj): + return obj.asset_set.all().count() + + +class AssetCategoryMiniSerializer(serializers.ModelSerializer): + class Meta: + model = AssetCategory + fields = ['id', 'asset_category_name'] + + + def get_asset_count(self, obj): + return obj.asset_set.all().count() + + +class AssetLotSerializer(serializers.ModelSerializer): + class Meta: + model = AssetLot + fields = '__all__' + + +class AssetGetAllSerializer(serializers.ModelSerializer): + class Meta: + model = Asset + fields = ['id', 'asset_name', 'asset_status'] + + +class AssetSerializer(serializers.ModelSerializer): + class Meta: + model = Asset + fields = '__all__' + + +class AssetAssignmentSerializer(serializers.ModelSerializer): + class Meta: + model = AssetAssignment + fields = '__all__' + + +class AssetAssignmentGetSerializer(serializers.ModelSerializer): + asset = serializers.SerializerMethodField() + asset_category = serializers.SerializerMethodField() + allocated_user = serializers.SerializerMethodField() + class Meta: + model = AssetAssignment + fields = ['id', 'asset', 'asset_category', 'allocated_user', 'assigned_date', 'return_status'] + + def get_asset(self, obj): + return obj.asset_id.asset_name + + def get_asset_category(self, obj): + return obj.asset_id.asset_category_id.asset_category_name + + def get_allocated_user(self, obj): + return EmployeeGetSerializer(obj.assigned_to_employee_id).data + + +class AssetRequestSerializer(serializers.ModelSerializer): + class Meta: + model = AssetRequest + fields = '__all__' + + +class AssetRequestGetSerializer(serializers.ModelSerializer): + asset_category_id = serializers.SerializerMethodField() + requested_employee_id = serializers.SerializerMethodField() + class Meta: + model = AssetRequest + fields = '__all__' + + def get_asset_category_id(self, obj): + return AssetCategoryMiniSerializer(obj.asset_category_id).data + + def get_requested_employee_id(self, obj): + return EmployeeGetSerializer(obj.requested_employee_id).data + + +class EmployeeGetSerializer(serializers.ModelSerializer): + full_name = serializers.SerializerMethodField() + + class Meta: + model = Employee + fields = ['id', 'full_name', 'employee_profile', 'badge_id'] + + def get_full_name(self, obj): + return obj.get_full_name() + + +class AssetApproveSerializer(serializers.ModelSerializer): + class Meta: + model = AssetAssignment + fields = ['id', 'asset_id', 'assigned_to_employee_id', 'assigned_by_employee_id', 'assign_images'] + + + + def validate_asset_id(self, value): + asset_request = self.context.get('asset_request') + asset_category = asset_request.asset_category_id + if value.asset_category_id != asset_category: + raise serializers.ValidationError("Invalid asset.") + return value + + +class AssetReturnSerializer(serializers.ModelSerializer): + return_status = serializers.CharField(required=True) + image = serializers.FileField(required=True) + + class Meta: + model = AssetAssignment + fields = ['return_status', 'return_condition', 'image'] + + def validate_return_status(self, value): + if value not in [status[0] for status in AssetAssignment.STATUS]: + raise serializers.ValidationError("Invalid Choice") + return value + + def validate(self, data): + if self.instance.return_date: + raise serializers.ValidationError("Already Returned") + return data + +# class ReturnImageSerializer(serializers.ModelSerializer): +# image = serializers.FileField(required=True) +# class Meta: +# model = ReturnImages +# fields = '__all__' + + diff --git a/horilla_api/api_serializers/attendance/serializers.py b/horilla_api/api_serializers/attendance/serializers.py new file mode 100644 index 000000000..2da1fecea --- /dev/null +++ b/horilla_api/api_serializers/attendance/serializers.py @@ -0,0 +1,200 @@ +from rest_framework import serializers +from attendance.models import * +from recruitment.models import RecruitmentMailTemplate + + +class AttendanceSerializer(serializers.ModelSerializer): + employee_first_name = serializers.CharField( + source="employee_id.employee_first_name", read_only=True) + employee_last_name = serializers.CharField( + source="employee_id.employee_last_name", read_only=True) + shift_name = serializers.CharField( + source="shift_id.employee_shift", read_only=True) + badge_id = serializers.CharField( + source="employee_id.badge_id", read_only=True) + employee_profile_url = serializers.SerializerMethodField(read_only=True) + work_type = serializers.CharField(source="work_type_id.work_type",read_only=True) + + class Meta: + model = Attendance + exclude = [ + "overtime_second", + "at_work_second", + "attendance_day", + "request_description", + "approved_overtime_second", + "request_type", + "requested_data", + "is_validate_request", + "is_validate_request_approved", + "attendance_overtime", + ] + + def validate(self, data): + # Check if attendance exists for the employee on the current date + if self.instance: + return data + employee_id = data.get('employee_id') + attendance_date = data.get('attendance_date', date.today()) + if Attendance.objects.filter(employee_id=employee_id, attendance_date=attendance_date).exists(): + raise ValidationError( + ("Attendance for this employee on the current date already exists.")) + return data + + def get_employee_profile_url(self, obj): + try: + employee_profile = obj.employee_id.employee_profile + return employee_profile.url + except: + return None + + +class AttendanceRequestSerializer(serializers.ModelSerializer): + employee_first_name = serializers.CharField( + source="employee_id.employee_first_name", read_only=True) + employee_last_name = serializers.CharField( + source="employee_id.employee_last_name", read_only=True) + shift_name = serializers.CharField( + source="shift_id.employee_shift", read_only=True) + badge_id = serializers.CharField( + source="employee_id.badge_id", read_only=True) + employee_profile_url = serializers.SerializerMethodField(read_only=True) + + class Meta: + model = Attendance + exclude = [ + "attendance_overtime", + "attendance_overtime_approve", + "attendance_validated", + "approved_overtime_second", + "is_validate_request", + "is_validate_request_approved", + "request_type", + "created_at", + ] + + def create(self, validated_data): + # Extract relevant data from validated_data + employee_id = validated_data.get("employee_id") + attendance_date = validated_data.get("attendance_date") + # Check if attendance exists for the employee and date + attendances = Attendance.objects.filter( + employee_id=employee_id, attendance_date=attendance_date + ) + data = { + "employee_id": validated_data.get("employee_id"), + "attendance_date": validated_data.get("attendance_date"), + "attendance_clock_in_date": validated_data.get("attendance_clock_in_date"), + "attendance_clock_in": validated_data.get("attendance_clock_in"), + "attendance_clock_out": validated_data.get("attendance_clock_out"), + "attendance_clock_out_date": validated_data.get("attendance_clock_out_date"), + "shift_id": validated_data.get("shift_id"), + "work_type_id": validated_data.get("work_type_id"), + "attendance_worked_hour": validated_data.get("attendance_worked_hour"), + "minimum_hour": validated_data.get("minimum_hour"), + } + if attendances.exists(): + data["employee_id"] = employee_id.id + data["attendance_date"] = str(attendance_date) + data["attendance_clock_in_date"] = self.data["attendance_clock_in_date"] + data["attendance_clock_in"] = self.data["attendance_clock_in"] + data["attendance_clock_out"] = ( + None + if data["attendance_clock_out"] == "None" + else data["attendance_clock_out"] + ) + data["attendance_clock_out_date"] = ( + None + if data["attendance_clock_out_date"] == "None" + else data["attendance_clock_out_date"] + ) + data["work_type_id"] = self.data["work_type_id"] + data["shift_id"] = self.data["shift_id"] + attendance = attendances.first() + for key, value in data.items(): + data[key] = str(value) + attendance.requested_data = json.dumps(data) + attendance.is_validate_request = True + if attendance.request_type != "create_request": + attendance.request_type = "update_request" + attendance.request_description = self.data["request_description"] + return attendance.save() + new_instance = Attendance(**data) + new_instance.is_validate_request = True + new_instance.attendance_validated = False + new_instance.request_description = self.data["request_description"] + new_instance.request_type = "create_request" + new_instance.save() + return new_instance + + def update(self, instance, validated_data): + if 'employee_id' in validated_data: + validated_data.pop('employee_id') + return super().update(instance, validated_data) + + def get_employee_profile_url(self, obj): + try: + employee_profile = obj.employee_id.employee_profile + return employee_profile.url + except: + return None + + +class AttendanceOverTimeSerializer(serializers.ModelSerializer): + badge_id = serializers.CharField( + source='employee_id.badge_id', read_only=True) + employee_first_name = serializers.CharField( + source="employee_id.employee_first_name", read_only=True) + employee_last_name = serializers.CharField( + source="employee_id.employee_last_name", read_only=True) + employee_profile_url = serializers.SerializerMethodField(read_only=True) + + class Meta: + model = AttendanceOverTime + fields = [ + "id", + "employee_first_name", + "employee_last_name", + "employee_profile_url", + "badge_id", + "employee_id", + "month", + "year", + "worked_hours", + "pending_hours", + "overtime", + ] + + def get_employee_profile_url(self, obj): + try: + employee_profile = obj.employee_id.employee_profile + return employee_profile.url + except: + return None + + +class AttendanceLateComeEarlyOutSerializer(serializers.ModelSerializer): + employee_first_name = serializers.CharField( + source="employee_id.employee_first_name", read_only=True) + employee_last_name = serializers.CharField( + source="employee_id.employee_last_name", read_only=True) + + class Meta: + model = AttendanceLateComeEarlyOut + fields = '__all__' + + +class AttendanceActivitySerializer(serializers.ModelSerializer): + employee_first_name = serializers.CharField( + source="employee_id.employee_first_name", read_only=True) + employee_last_name = serializers.CharField( + source="employee_id.employee_last_name", read_only=True) + + class Meta: + model = AttendanceActivity + fields = "__all__" + +class MailTemplateSerializer(serializers.ModelSerializer): + class Meta: + model = RecruitmentMailTemplate + fields = "__all__" diff --git a/horilla_api/api_serializers/auth/serializers.py b/horilla_api/api_serializers/auth/serializers.py new file mode 100644 index 000000000..84b63d7d6 --- /dev/null +++ b/horilla_api/api_serializers/auth/serializers.py @@ -0,0 +1,12 @@ +from rest_framework import serializers +from employee.models import Employee + + +class GetEmployeeSerializer(serializers.ModelSerializer): + full_name = serializers.SerializerMethodField() + class Meta: + model = Employee + fields = ['id', 'full_name', 'employee_profile'] + + def get_full_name(self, obj): + return obj.get_full_name() diff --git a/horilla_api/api_serializers/base/serializers.py b/horilla_api/api_serializers/base/serializers.py new file mode 100644 index 000000000..873c084f8 --- /dev/null +++ b/horilla_api/api_serializers/base/serializers.py @@ -0,0 +1,398 @@ +import django +from django.core.exceptions import ValidationError as DjangoValidationError +from datetime import timezone +from rest_framework import serializers +from rest_framework.serializers import ValidationError +from base.models import Company, Department, EmployeeShift, EmployeeShiftDay, EmployeeShiftSchedule, JobPosition, JobRole, RotatingShift, RotatingShiftAssign, RotatingWorkType, RotatingWorkTypeAssign, ShiftRequest, WorkType, WorkTypeRequest +from employee.models import Actiontype +from employee.models import Employee +from django.http import QueryDict + + +class CompanySerializer(serializers.ModelSerializer): + class Meta: + model = Company + fields = "__all__" + + +class JobPositionSerializer(serializers.ModelSerializer): + class Meta: + model = JobPosition + fields = "__all__" + + +class JobRoleSerializer(serializers.ModelSerializer): + class Meta: + model = JobRole + fields = "__all__" + + +class DepartmentSerializer(serializers.ModelSerializer): + class Meta: + model = Department + fields = "__all__" + + def create(self, validated_data): + comapny_id = validated_data.pop('company_id', []) + obj = Department(**validated_data) + obj.save() + obj.company_id.set(comapny_id) + return obj + + +class WorkTypeSerializer(serializers.ModelSerializer): + + class Meta: + model = WorkType + fields = '__all__' + + def validate(self, attrs): + # Create an instance of the model with the provided data + instance = WorkType(**attrs) + + # Call the model's clean method for validation + try: + instance.clean() + except DjangoValidationError as e: + # Raise DRF's ValidationError with the same message + raise serializers.ValidationError(e) + + return attrs + + def create(self, validated_data): + return super().create(validated_data) + + def update(self, instance, validated_data): + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.clean() # Call clean method before saving the instance + instance.save() + return instance + + +class RotatingWorkTypeSerializer(serializers.ModelSerializer): + class Meta: + model = RotatingWorkType + fields = '__all__' + + def validate(self, attrs): + # Create an instance of the model with the provided data + instance = RotatingWorkType(**attrs) + + # Call the model's clean method for validation + try: + instance.clean() + except DjangoValidationError as e: + # Raise DRF's ValidationError with the same message + raise serializers.ValidationError(e) + + return attrs + + def create(self, validated_data): + return super().create(validated_data) + + def update(self, instance, validated_data): + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.clean() # Call clean method before saving the instance + instance.save() + return instance + + +class RotatingWorkTypeAssignSerializer(serializers.ModelSerializer): + rotating_work_type_name = serializers.SerializerMethodField(read_only=True) + current_work_type_name = serializers.SerializerMethodField(read_only=True) + next_work_type_name = serializers.SerializerMethodField(read_only=True) + + class Meta: + model = RotatingWorkTypeAssign + fields = '__all__' + + def get_current_work_type_name(self, instance): + current_work_type = instance.current_work_type + if current_work_type: + return current_work_type.work_type + else: + return None # Return null if previous_work_type_id doesn't exist + + def get_next_work_type_name(self, instance): + next_work_type = instance.next_work_type + if next_work_type: + return next_work_type.work_type + else: + return None # Return null if previous_work_type_id doesn't exist + + def get_rotating_work_type_name(self, instance): + rotating_work_type_id = instance.rotating_work_type_id + if rotating_work_type_id: + return rotating_work_type_id.name + else: + return None # Return null if previous_work_type_id doesn't exist + + def validate(self, attrs): + if self.instance: + return attrs + # Create an instance of the model with the provided data + instance = RotatingWorkTypeAssign(**attrs) + # Call the model's clean method for validation + try: + instance.clean() + except DjangoValidationError as e: + # Raise DRF's ValidationError with the same message + raise serializers.ValidationError(e) + return attrs + + def create(self, validated_data): + return super().create(validated_data) + + def update(self, instance, validated_data): + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.save() + return instance + + +class EmployeeShiftDaySerializer(serializers.ModelSerializer): + class Meta: + model = EmployeeShiftDay + fields = '__all__' + + +class EmployeeShiftSerializer(serializers.ModelSerializer): + class Meta: + model = EmployeeShift + fields = '__all__' + + def validate(self, attrs): + # Create an instance of the model with the provided data + instance = EmployeeShift(**attrs) + + # Call the model's clean method for validation + try: + instance.clean() + except DjangoValidationError as e: + # Raise DRF's ValidationError with the same message + raise serializers.ValidationError(e) + + return attrs + + def create(self, validated_data): + return super().create(validated_data) + + def update(self, instance, validated_data): + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.clean() # Call clean method before saving the instance + instance.save() + return instance + + +class EmployeeShiftScheduleSerializer(serializers.ModelSerializer): + class Meta: + model = EmployeeShiftSchedule + fields = '__all__' + + +class RotatingShiftSerializer(serializers.ModelSerializer): + class Meta: + model = RotatingShift + fields = '__all__' + + def validate(self, attrs): + # Create an instance of the model with the provided data + instance = RotatingShift(**attrs) + + # Call the model's clean method for validation + try: + instance.clean() + except DjangoValidationError as e: + # Raise DRF's ValidationError with the same message + raise serializers.ValidationError(e) + + return attrs + + def create(self, validated_data): + return super().create(validated_data) + + def update(self, instance, validated_data): + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.clean() # Call clean method before saving the instance + instance.save() + return instance + + +class RotatingShiftAssignSerializer(serializers.ModelSerializer): + current_shift_name = serializers.SerializerMethodField(read_only=True) + next_shift_name = serializers.SerializerMethodField(read_only=True) + rotating_shift_name = serializers.SerializerMethodField(read_only=True) + rotate = serializers.CharField(read_only=True) + + class Meta: + model = RotatingShiftAssign + fields = '__all__' + + def validate(self, attrs): + # Create an instance of the model with the provided data + instance = RotatingShiftAssign(**attrs) + + # Call the model's clean method for validation + try: + instance.clean() + except DjangoValidationError as e: + # Raise DRF's ValidationError with the same message + raise serializers.ValidationError(e) + + return attrs + + def create(self, validated_data): + return super().create(validated_data) + + def to_representation(self, instance): + representation = super().to_representation(instance) + + if instance.based_on == 'after': + representation['rotate'] = f"Rotate after {instance.rotate_after_day} days" + elif instance.based_on == 'weekly': + representation['rotate'] = f"Weekly every {instance.rotate_every_weekend}" + elif instance.based_on == 'monthly': + if instance.rotate_every == '1': + representation['rotate'] = f"Rotate every {instance.rotate_every}st day of month" + elif instance.rotate_every == '2': + representation['rotate'] = f"Rotate every {instance.rotate_every}nd day of month" + elif instance.rotate_every == '3': + representation['rotate'] = f"Rotate every {instance.rotate_every}rd day of month" + elif instance.rotate_every == 'last': + representation['rotate'] = "Rotate every last day of month" + else: + representation['rotate'] = f"Rotate every {instance.rotate_every}th day of month" + + return representation + + def get_rotating_shift_name(self, instance): + rotating_shift_id = instance.rotating_shift_id + if rotating_shift_id: + return rotating_shift_id.name + else: + return None # Return null if previous_work_type_id doesn't exist + + def get_next_shift_name(self, instance): + next_shift = instance.next_shift + if next_shift: + return next_shift.employee_shift + else: + return None # Return null if previous_work_type_id doesn't exist + + def get_current_shift_name(self, instance): + current_shift = instance.current_shift + if current_shift: + return current_shift.employee_shift + else: + return None # Return null if previous_work_type_id doesn't exist + + def update(self, instance, validated_data): + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.clean() # Call clean method before saving the instance + instance.save() + return instance + + +class WorkTypeRequestSerializer(serializers.ModelSerializer): + employee_first_name = serializers.CharField( + source='employee_id.employee_first_name', read_only=True) + employee_last_name = serializers.CharField( + source='employee_id.employee_last_name', read_only=True) + work_type_name = serializers.CharField( + source='work_type_id.work_type', read_only=True) + previous_work_type_name = serializers.SerializerMethodField(read_only=True) + + class Meta: + model = WorkTypeRequest + fields = '__all__' + + def validate(self, attrs): + # Create an instance of the model with the provided data + instance = WorkTypeRequest(**attrs) + + # Call the model's clean method for validation + try: + instance.clean() + except DjangoValidationError as e: + # Raise DRF's ValidationError with the same message + raise serializers.ValidationError(e) + + return attrs + + def create(self, validated_data): + return super().create(validated_data) + + def get_previous_work_type_name(self, instance): + previous_work_type = instance.previous_work_type_id + if previous_work_type: + return previous_work_type.work_type + else: + return None # Return null if previous_work_type_id doesn't exist + + def update(self, instance, validated_data): + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.clean() # Call clean method before saving the instance + instance.save() + return instance + + +class ShiftRequestSerializer(serializers.ModelSerializer): + employee_first_name = serializers.CharField( + source='employee_id.employee_first_name', read_only=True) + employee_last_name = serializers.CharField( + source='employee_id.employee_last_name', read_only=True) + shift_name = serializers.SerializerMethodField(read_only=True) + previous_shift_name = serializers.SerializerMethodField(read_only=True) + + def get_previous_shift_name(self, instance): + previous_shift_id = instance.previous_shift_id + if previous_shift_id: + return previous_shift_id.employee_shift + else: + return None # Re + + def get_shift_name(self, instance): + shift_id = instance.shift_id + if shift_id: + return shift_id.employee_shift + else: + return None # Re + + def validate(self, attrs): + # Create an instance of the model with the provided data + instance = ShiftRequest(**attrs) + + # Call the model's clean method for validation + try: + instance.clean() + except DjangoValidationError as e: + # Raise DRF's ValidationError with the same message + raise serializers.ValidationError(e) + + return attrs + + def create(self, validated_data): + return super().create(validated_data) + + def update(self, instance, validated_data): + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.clean() # Call clean method before saving the instance + instance.save() + return instance + + class Meta: + model = ShiftRequest + fields = '__all__' + + +class ActiontypeSerializer(serializers.ModelSerializer): + class Meta: + model = Actiontype + fields = ['id', 'title', 'action_type'] diff --git a/horilla_api/api_serializers/employee/serializers.py b/horilla_api/api_serializers/employee/serializers.py new file mode 100644 index 000000000..e5ee492ca --- /dev/null +++ b/horilla_api/api_serializers/employee/serializers.py @@ -0,0 +1,128 @@ +from employee.models import Policy +from rest_framework import serializers +from base.models import Department, EmployeeType, JobPosition + +from employee.models import DisciplinaryAction, Employee, EmployeeBankDetails, EmployeeWorkInformation +from ...api_methods.employee.methods import get_next_badge_id +from horilla_documents.models import Document, DocumentRequest + + +class EmployeeListSerializer(serializers.ModelSerializer): + job_position_name = serializers.CharField( + source='employee_work_info.job_position_id.job_position', read_only=True) + employee_work_info_id = serializers.CharField( + source="employee_work_info.id", read_only=True) + employee_bank_details_id = serializers.CharField( + source="employee_bank_details.id", read_only=True) + + class Meta: + model = Employee + fields = ['id','employee_first_name', 'employee_last_name', + 'email', 'job_position_name', 'employee_work_info_id', 'employee_profile', 'employee_bank_details_id'] + + +class EmployeeSerializer(serializers.ModelSerializer): + job_position_name = serializers.CharField( + source='employee_work_info.job_position_id.job_position', read_only=True) + job_position_id = serializers.CharField( + source='employee_work_info.job_position_id.id', read_only=True) + employee_work_info_id = serializers.CharField( + source="employee_work_info.id", read_only=True) + employee_bank_details_id = serializers.CharField( + source="employee_bank_details.id", read_only=True) + + class Meta: + model = Employee + fields = "__all__" + + def create(self, validated_data): + validated_data['badge_id'] = get_next_badge_id() + return super().create(validated_data) + + +class EmployeeWorkInformationSerializer(serializers.ModelSerializer): + job_position_name = serializers.CharField( + source='job_position_id.job_position', read_only=True) + department_name = serializers.CharField( + source='department_id.department', read_only=True) + shift_name = serializers.CharField( + source='shift_id.employee_shift', read_only=True) + employee_type_name = serializers.CharField( + source='employee_type_id.employee_type', read_only=True) + reporting_manager_first_name = serializers.CharField( + source='reporting_manager_id.employee_first_name', read_only=True) + reporting_manager_last_name = serializers.CharField( + source='reporting_manager_id.employee_last_name', read_only=True) + work_type_name = serializers.CharField( + source='work_type_id.work_type', read_only=True) + company_name = serializers.CharField( + source='company_id.company', read_only=True) + + class Meta: + model = EmployeeWorkInformation + fields = "__all__" + + +class EmployeeBankDetailsSerializer(serializers.ModelSerializer): + class Meta: + model = EmployeeBankDetails + fields = '__all__' + +class EmployeeTypeSerializer(serializers.ModelSerializer): + class Meta: + model = EmployeeType + fields = '__all__' + + +class EmployeeBulkUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = Employee + # fields = [ + # 'employee_last_name', + # 'address', + # 'country', + # 'state', + # 'city', + # 'zip', + # 'dob', + # 'gender', + # 'qualification', + # 'experience', + # 'marital_status', + # 'children', + # ] + fields = [ + 'employee_last_name', + + ] + + +class DisciplinaryActionSerializer(serializers.ModelSerializer): + class Meta: + model = DisciplinaryAction + fields = '__all__' + + +class PolicySerializer(serializers.ModelSerializer): + class Meta: + model = Policy + fields = '__all__' + + + +class DocumentRequestSerializer(serializers.ModelSerializer): + class Meta: + model = DocumentRequest + fields = '__all__' + + +class DocumentSerializer(serializers.ModelSerializer): + class Meta: + model = Document + fields = '__all__' + + +class EmployeeSelectorSerializer(serializers.ModelSerializer): + class Meta: + model = Employee + fields = ['id','employee_first_name','employee_last_name','badge_id','employee_profile'] diff --git a/horilla_api/api_serializers/leave/serializers.py b/horilla_api/api_serializers/leave/serializers.py new file mode 100644 index 000000000..b92ec8733 --- /dev/null +++ b/horilla_api/api_serializers/leave/serializers.py @@ -0,0 +1,504 @@ +from rest_framework import serializers +from leave.models import * +from leave.forms import calculate_requested_days, cal_effective_requested_days +from employee.models import Employee + + +def leave_Validations(self, data): + start_date = data.get("start_date") + end_date = data.get("end_date") + start_date_breakdown = ( + data.get("start_date_breakdown") + if data.get("start_date_breakdown") is not None + else "full_day" + ) + end_date_breakdown = ( + data.get("end_date_breakdown") + if data.get("end_date_breakdown") is not None + else "full_day" + ) + employee = data.get("employee_id") + leave_type_id = data.get("leave_type_id") + attachment = data.get("attachment") + available_leave = ( + AvailableLeave.objects.filter( + leave_type_id=leave_type_id, employee_id=employee + )[0] + if AvailableLeave.objects.filter( + leave_type_id=leave_type_id, employee_id=employee + ).exists() + else None + ) + if not available_leave: + raise serializers.ValidationError( + f"Employee is not assigned with leave type {leave_type_id}." + ) + + requested_days = calculate_requested_days( + start_date, end_date, start_date_breakdown, end_date_breakdown + ) + effective_requested_days = cal_effective_requested_days( + start_date=start_date, + end_date=end_date, + leave_type_id=leave_type_id, + requested_days=requested_days, + ) + + total_leave_days = ( + available_leave.available_days + available_leave.carryforward_days + ) + errors = {} + # checking if there is any requested days is overlapping with the existing leave request + leave_requests = employee.leaverequest_set.filter( + start_date__lte=end_date, end_date__gte=start_date + ) + if self.instance: + leave_requests = leave_requests.exclude(id=self.instance.id) + if leave_requests: + raise serializers.ValidationError( + "There is already a leave request for this date range." + ) + + # checking if the end date is less than the start date + if not start_date <= end_date: + errors["end_date"] = ["End date should not be less than start date."] + + if start_date == end_date and start_date_breakdown != end_date_breakdown: + raise serializers.ValidationError( + "There is a mismatch in the breakdown of the start date and end date." + ) + + if not effective_requested_days <= total_leave_days: + raise serializers.ValidationError("Employee doesn't have enough leave days..") + + if leave_type_id.require_attachment == "yes" and attachment == None: + errors["attachment"] = ["This field is required."] + + if errors: + raise serializers.ValidationError(errors) + + +class GetAvailableLeaveTypeSerializer(serializers.ModelSerializer): + leave_type_id = serializers.SerializerMethodField() + icon = serializers.SerializerMethodField() + + class Meta: + model = AvailableLeave + fields = [ + "id", + "leave_type_id", + "icon", + "available_days", + "carryforward_days", + "total_leave_days", + ] + + def get_leave_type_id(self, obj): + if obj.leave_type_id: + return LeaveTypeAllGetSerializer(obj.leave_type_id).data + return None + + def get_icon(self, obj): + try: + return obj.leave_type_id.icon.url + except: + return None + + +class GetAvailableLeaveTypeSerializer(serializers.ModelSerializer): + leave_type_id = serializers.SerializerMethodField() + icon = serializers.SerializerMethodField() + total_leave_days = serializers.SerializerMethodField() + + class Meta: + model = AvailableLeave + fields = [ + "id", + "leave_type_id", + "icon", + "available_days", + "carryforward_days", + "total_leave_days", + ] + + def get_leave_type_id(self, obj): + if obj.leave_type_id: + return LeaveTypeAllGetSerializer(obj.leave_type_id).data + return None + + def get_icon(self, obj): + try: + return obj.leave_type_id.icon.url + except: + return None + + def get_total_leave_days(self, obj): + return obj.available_days + obj.carryforward_days + + +class userLeaveRequestGetAllSerilaizer(serializers.ModelSerializer): + leave_type_id = serializers.SerializerMethodField() + + class Meta: + model = LeaveRequest + exclude = [ + "requested_date", + "description", + "attachment", + "approved_available_days", + "approved_carryforward_days", + "created_at", + "reject_reason", + "employee_id", + "created_by", + ] + + def get_leave_type_id(self, obj): + if obj.leave_type_id: + return LeaveTypeAllGetSerializer(obj.leave_type_id).data + return None + + +class UserLeaveRequestGetSerilaizer(serializers.ModelSerializer): + leave_type_id = serializers.SerializerMethodField() + + class Meta: + model = LeaveRequest + exclude = [ + "requested_date", + "approved_available_days", + "approved_carryforward_days", + "created_at", + "reject_reason", + "employee_id", + "created_by", + ] + + def get_leave_type_id(self, obj): + if obj.leave_type_id: + return LeaveTypeAllGetSerializer(obj.leave_type_id).data + return None + + +class LeaveRequestCreateUpdateSerializer(serializers.ModelSerializer): + + class Meta: + model = LeaveRequest + fields = [ + "employee_id", + "leave_type_id", + "start_date", + "start_date_breakdown", + "end_date", + "end_date_breakdown", + "description", + "attachment", + ] + + def validate(self, data): + leave_Validations(self, data) + return data + + +class UpdateLeaveRequestSerializer(serializers.ModelSerializer): + + class Meta: + model = LeaveRequest + fields = [ + "start_date", + "start_date_breakdown", + "end_date", + "end_date_breakdown", + "description", + "attachment", + ] + + def validate(self, data): + leave_Validations(self, data) + return data + + +class LeaveTypeGetCreateSerilaizer(serializers.ModelSerializer): + class Meta: + model = LeaveType + fields = "__all__" + + def validate(self, data): + reset = data.get("reset") + reset_based = data.get("reset_based") + reset_month = data.get("reset_month") + reset_day = data.get("reset_day") + reset_weekday = data.get("reset_weekday") + carryforward_type = data.get("carryforward_type") + carryforward_max = data.get("carryforward_max") + if reset == True: + if reset_based == None: + raise serializers.ValidationError( + {"reset_based": ["This field is required."]} + ) + elif reset_based == "yearly" and reset_month == None: + raise serializers.ValidationError( + {"reset_month": ["This field is required."]} + ) + elif reset_based in ["yearly", "monthly"] and reset_day == "": + raise serializers.ValidationError( + {"reset_day": ["This field is required."]} + ) + elif reset_based == "weekly" and reset_weekday == None: + raise serializers.ValidationError( + {"reset_weekday": ["This field is required."]} + ) + # elif carryforward_type in ['carryforward', 'carryforward expire'] and carryforward_max + return data + + +class LeaveTypeAllGetSerializer(serializers.ModelSerializer): + class Meta: + model = LeaveType + fields = ["id", "name", "icon"] + + +class LeaveAllocationRequestCreateSerializer(serializers.ModelSerializer): + requested_days = serializers.FloatField(required=True) + + class Meta: + model = LeaveAllocationRequest + fields = [ + "leave_type_id", + "employee_id", + "requested_days", + "created_by", + "description", + "attachment", + ] + + +class AssignLeaveCreateSerializer(serializers.Serializer): + leave_type_ids = serializers.PrimaryKeyRelatedField( + many=True, queryset=LeaveType.objects.all() + ) + employee_ids = serializers.PrimaryKeyRelatedField( + many=True, queryset=Employee.objects.all() + ) + + def validate_leave_type_ids(self, value): + if not value: + raise serializers.ValidationError( + {"leave_type_ids": ["This field is required."]} + ) + return value + + def validate_employee_ids(self, value): + if not value: + raise serializers.ValidationError( + {"employee_ids": ["This field is required."]} + ) + return value + + +class AssignLeaveGetSerializer(serializers.ModelSerializer): + + employee_id = serializers.SerializerMethodField() + leave_type_id = serializers.SerializerMethodField() + + class Meta: + model = AvailableLeave + exclude = ["reset_date", "expired_date"] + + def get_employee_id(self, obj): + employee = obj.employee_id + if employee: + return EmployeeGetSerializer(employee).data + return None + + def get_leave_type_id(self, obj): + if obj.leave_type_id: + return LeaveTypeAllGetSerializer(obj.leave_type_id).data + return None + + +class EmployeeGetSerializer(serializers.ModelSerializer): + full_name = serializers.SerializerMethodField() + + class Meta: + model = Employee + fields = ["id", "full_name", "employee_profile", "badge_id"] + + def get_full_name(self, obj): + return obj.get_full_name() + + +class AvailableLeaveUpdateSerializer(serializers.ModelSerializer): + available_days = serializers.FloatField(required=True) + + class Meta: + model = AvailableLeave + fields = ["available_days", "carryforward_days"] + + +class LeaveRequestGetAllSerilaizer(serializers.ModelSerializer): + employee_id = serializers.SerializerMethodField() + leave_type_id = serializers.SerializerMethodField() + multiple_approve = serializers.SerializerMethodField() + + class Meta: + model = LeaveRequest + exclude = [ + "requested_date", + "description", + "attachment", + "approved_available_days", + "approved_carryforward_days", + "created_at", + "reject_reason", + "created_by", + ] + + def get_employee_id(self, obj): + employee = obj.employee_id + if employee: + return EmployeeGetSerializer(employee).data + return None + + def get_leave_type_id(self, obj): + if obj.leave_type_id: + return LeaveTypeAllGetSerializer(obj.leave_type_id).data + return None + + def get_multiple_approve(self, obj): + approvals = LeaveRequestConditionApproval.objects.filter(leave_request_id=obj) + employee = self.context["request"].user.employee_get + if approvals and obj.status == "requested": + is_approved = approvals.filter(is_approved=True) + count = f"{is_approved.count()} / {approvals.count()}" + is_approved = ( + True if is_approved.filter(manager_id=employee).exists() else False + ) + return {"count": count, "is_approved": is_approved} + return None + + +class LeaveRequestGetSerilaizer(serializers.ModelSerializer): + employee_id = serializers.SerializerMethodField() + leave_type_id = serializers.SerializerMethodField() + multiple_approve = serializers.SerializerMethodField() + + class Meta: + model = LeaveRequest + exclude = [ + "requested_date", + "approved_available_days", + "approved_carryforward_days", + "created_at", + "reject_reason", + "created_by", + ] + + def get_employee_id(self, obj): + employee = obj.employee_id + if employee: + return EmployeeGetSerializer(employee).data + return None + + def get_leave_type_id(self, obj): + if obj.leave_type_id: + return LeaveTypeAllGetSerializer(obj.leave_type_id).data + return None + + def get_multiple_approve(self, obj): + approvals = LeaveRequestConditionApproval.objects.filter(leave_request_id=obj) + employee = self.context["request"].user.employee_get + if approvals and obj.status == "requested": + is_approved = approvals.filter(is_approved=True) + count = f"{is_approved.count()} / {approvals.count()}" + is_approved = ( + True if is_approved.filter(manager_id=employee).exists() else False + ) + return {"count": count, "is_approved": is_approved} + return None + + +class LeaveAllocationRequestSerilaizer(serializers.ModelSerializer): + class Meta: + model = LeaveAllocationRequest + exclude = [ + "requested_date", + "status", + "created_by", + "created_at", + "reject_reason", + ] + + +class LeaveAllocationRequestGetSerializer(serializers.ModelSerializer): + employee_id = serializers.SerializerMethodField() + leave_type_id = serializers.SerializerMethodField() + created_by = serializers.SerializerMethodField() + + class Meta: + model = LeaveAllocationRequest + exclude = ["requested_date", "created_at", "reject_reason"] + + def get_employee_id(self, obj): + employee = obj.employee_id + if employee: + return EmployeeGetSerializer(employee).data + return None + + def get_leave_type_id(self, obj): + if obj.leave_type_id: + return LeaveTypeAllGetSerializer(obj.leave_type_id).data + return None + + def get_created_by(self, obj): + created_by = obj.created_by + if created_by: + return EmployeeGetSerializer(created_by).data + return None + + +class CompanyLeaveSerializer(serializers.ModelSerializer): + class Meta: + model = CompanyLeave + exclude = ["company_id"] + + +class HoildaySerializer(serializers.ModelSerializer): + class Meta: + model = Holiday + exclude = ["company_id"] + + def validate(self, data): + start_date = data.get("start_date") + end_date = data.get("end_date") + if end_date and not start_date <= end_date: + raise serializers.ValidationError( + {"end_date": ["End date should not be less than start date."]} + ) + return data + + +class LeaveRequestApproveSerializer(serializers.ModelSerializer): + + class Meta: + model = LeaveRequest + fields = [] + + def validate(self, data): + leave_request = self.instance + if leave_request.status != "requested": + raise serializers.ValidationError("Nothing to approve.") + employee_id = leave_request.employee_id + leave_type_id = leave_request.leave_type_id + available_leave = AvailableLeave.objects.get( + leave_type_id=leave_type_id, employee_id=employee_id + ) + total_available_leave = ( + available_leave.available_days + available_leave.carryforward_days + ) + if not total_available_leave >= leave_request.requested_days: + raise serializers.ValidationError( + f"{employee_id} dont have enough leave days to approve the request.." + ) + data["available_leave"] = available_leave + return data diff --git a/horilla_api/api_serializers/notifications/serializers.py b/horilla_api/api_serializers/notifications/serializers.py new file mode 100644 index 000000000..3d356b1a9 --- /dev/null +++ b/horilla_api/api_serializers/notifications/serializers.py @@ -0,0 +1,8 @@ +from notifications . models import Notification +from rest_framework import serializers + + +class NotificationSerializer(serializers.ModelSerializer): + class Meta: + model = Notification + fields = ['id','level', 'unread', 'verb', 'timestamp', 'deleted', 'data'] diff --git a/horilla_api/api_serializers/payroll/serializers.py b/horilla_api/api_serializers/payroll/serializers.py new file mode 100644 index 000000000..db53e0c85 --- /dev/null +++ b/horilla_api/api_serializers/payroll/serializers.py @@ -0,0 +1,279 @@ +from rest_framework import serializers +from employee.models import BonusPoint, Employee +from leave.models import LeaveType +from payroll.models.models import Allowance, Contract, Deduction, LoanAccount, MultipleCondition, Payslip, Reimbursement, ReimbursementMultipleAttachment +from payroll.models.tax_models import TaxBracket + + +class PayslipSerializer(serializers.ModelSerializer): + employee_first_name = serializers.CharField( + source="employee_id.employee_first_name", read_only=True) + employee_last_name = serializers.CharField( + source="employee_id.employee_last_name", read_only=True) + shift_name = serializers.CharField( + source="shift_id.employee_shift", read_only=True) + badge_id = serializers.CharField( + source="employee_id.badge_id", read_only=True) + employee_profile_url = serializers.SerializerMethodField(read_only=True) + department_name = serializers.CharField( + source='employee_id.employee_work_info.department_id.department', read_only=True) + bank_account_check_number = serializers.CharField( + source='employee_id.employee_bank_details.account_number', read_only=True) + + + class Meta: + model = Payslip + fields = '__all__' + # exclude = ['reference', + # 'sent_to_employee', + # 'installment_ids', 'created_at'] + + + def get_employee_profile_url(self, obj): + try: + employee_profile = obj.employee_id.employee_profile + return employee_profile.url + except: + return None + +class ContractSerializer(serializers.ModelSerializer): + employee_first_name = serializers.CharField( + source="employee_id.employee_first_name", read_only=True) + employee_last_name = serializers.CharField( + source="employee_id.employee_last_name", read_only=True) + shift_name = serializers.CharField( + source="shift_id.employee_shift", read_only=True) + badge_id = serializers.CharField( + source="employee_id.badge_id", read_only=True) + employee_profile_url = serializers.SerializerMethodField(read_only=True) + + job_position_name = serializers.CharField( + source='job_position_id.job_position', read_only=True) + job_role_name = serializers.CharField( + source='job_role_id.job_role', read_only=True) + department_name = serializers.CharField( + source='department_id.department', read_only=True) + shift_name = serializers.CharField( + source='shift_id.employee_shift', read_only=True) + work_type_name = serializers.CharField( + source='work_type_id.work_type', read_only=True) + def get_employee_profile_url(self, obj): + try: + employee_profile = obj.employee_id.employee_profile + return employee_profile.url + except: + return None + + class Meta: + model = Contract + fields = '__all__' + + +class MultipleConditionSerializer(serializers.ModelSerializer): + class Meta: + model = MultipleCondition + fields = '__all__' + + +class AllowanceSerializer(serializers.ModelSerializer): + specific_employees = serializers.PrimaryKeyRelatedField( + queryset=Employee.objects.all(), many=True, required=False + ) + exclude_employees = serializers.PrimaryKeyRelatedField( + queryset=Employee.objects.all(), many=True, required=False + ) + other_conditions = serializers.PrimaryKeyRelatedField( + queryset=MultipleCondition.objects.all(), many=True, required=False + ) + + class Meta: + model = Allowance + fields = '__all__' + read_only_fields = ['id', 'company_id', 'only_show_under_employee', 'is_loan'] + + def create(self, validated_data): + specific_employees = validated_data.pop('specific_employees', []) + exclude_employees = validated_data.pop('exclude_employees', []) + other_conditions = validated_data.pop('other_conditions', []) + + allowance = Allowance.objects.create(**validated_data) + + # Set the ManyToMany relationships after the instance is created + allowance.specific_employees.set(specific_employees) + allowance.exclude_employees.set(exclude_employees) + allowance.other_conditions.set(other_conditions) + + return allowance + + def validate(self, data): + is_fixed = data.get('is_fixed') + amount = data.get('amount') + based_on = data.get('based_on') + per_attendance_fixed_amount = data.get('per_attendance_fixed_amount') + shift_id = data.get('shift_id') + work_type_id = data.get('work_type_id') + is_condition_based = data.get('is_condition_based') + field = data.get('field') + condition = data.get('condition') + value = data.get('value') + has_max_limit = data.get('has_max_limit') + maximum_amount = data.get('maximum_amount') + + if is_fixed and (amount is None or amount < 0): + raise serializers.ValidationError("If 'is_fixed' is True, 'amount' must be a positive number.") + + if not is_fixed and not based_on: + raise serializers.ValidationError("If 'is_fixed' is False, 'based_on' is required.") + + if based_on == "attendance" and not per_attendance_fixed_amount: + raise serializers.ValidationError("If 'based_on' is 'attendance', 'per_attendance_fixed_amount' is required.") + + if based_on == "shift_id" and not shift_id: + raise serializers.ValidationError("If 'based_on' is 'shift_id', 'shift_id' is required.") + + if based_on == "work_type_id" and not work_type_id: + raise serializers.ValidationError("If 'based_on' is 'work_type_id', 'work_type_id' is required.") + + if is_condition_based and (not field or not value or not condition): + raise serializers.ValidationError("If 'is_condition_based' is True, 'field', 'value', and 'condition' are required.") + + if has_max_limit and maximum_amount is None: + raise serializers.ValidationError("If 'has_max_limit' is True, 'maximum_amount' is required.") + + return data +class DeductionSerializer(serializers.ModelSerializer): + class Meta: + model = Deduction + fields = '__all__' + + +class LoanAccountSerializer(serializers.ModelSerializer): + employee_profile_url = serializers.SerializerMethodField(read_only=True) + employee_full_name = serializers.CharField(source='employee_id.get_full_name',read_only=True) + badge_id = serializers.CharField(source='employee_id.badge_id',read_only=True) + job_position_name = serializers.CharField(source='employee_id.get_job_position',read_only=True) + class Meta: + model = LoanAccount + fields = '__all__' + + def get_employee_profile_url(self, obj): + try: + employee_profile = obj.employee_id.employee_profile + return employee_profile.url + except: + return None + + +class ReimbursementSerializer(serializers.ModelSerializer): + other_attachements = serializers.SerializerMethodField() + leave_type_name = serializers.CharField(source='leave_type_id.name',read_only=True) + employee_profile_url = serializers.SerializerMethodField(read_only=True) + badge_id = serializers.CharField(source='employee_id.badge_id') + employee_full_name = serializers.CharField(source='employee_id.get_full_name') + + def get_employee_profile_url(self, obj): + try: + employee_profile = obj.employee_id.employee_profile + return employee_profile.url + except: + return None + + class Meta: + model = Reimbursement + fields = '__all__' + + def get_other_attachements(self, obj): + attachments = [] + for attachment in obj.other_attachments.all(): + try: + attachments.append(attachment.attachment.url) + except : + pass + return attachments + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + exclude_fields = [] + # Get type from data or instance + instance_type = getattr(self.instance, 'type', None) + + if instance_type == "reimbursement": + exclude_fields.extend( + ["leave_type_id", "cfd_to_encash", "ad_to_encash", "bonus_to_encash"]) + elif instance_type == "leave_encashment": + exclude_fields.extend(["attachment", "amount", "bonus_to_encash"]) + elif instance_type == "bonus_encashment": + exclude_fields.extend( + ["attachment", "amount", "leave_type_id", "cfd_to_encash", "ad_to_encash"]) + + # Remove excluded fields from serializer fields + for field in exclude_fields: + self.fields.pop(field, None) + + def get_encashable_leaves(self, employee): + leaves = LeaveType.objects.filter( + employee_available_leave__employee_id=employee, + employee_available_leave__total_leave_days__gte=1, + is_encashable=True, + ) + return leaves + + def validate(self, data): + try: + employee_id = self.instance.employee_id + type = self.instance.type + leave_type_id = self.instance.leave_type_id + except: + employee_id = data["employee_id"] + type = data["type"] + leave_type_id = data["leave_type_id"] if data.get("leave_type_id",None) else None + + available_points = BonusPoint.objects.filter( + employee_id=employee_id + ).first() + if type == "bonus_encashment": + try: + bonus_to_encash = self.instance.bonus_to_encash + except: + bonus_to_encash = data["bonus_to_encash"] + + if available_points.points < bonus_to_encash: + raise serializers.ValidationError( + {"bonus_to_encash": "Not enough bonus points to redeem"} + ) + if bonus_to_encash <= 0: + raise serializers.ValidationError( + { + "bonus_to_encash": "Points must be greater than zero to redeem." + } + ) + if type == "leave_encashment": + leave_type_id = leave_type_id + encashable_leaves = self.get_encashable_leaves(employee_id) + if (leave_type_id is None) or (leave_type_id not in encashable_leaves): + raise serializers.ValidationError( + {"leave_type_id": "This leave type is not encashable"} + ) + + return data + + def save(self, **kwargs): + multiple_attachment_ids = [] + request_files = self.context['request'].FILES + attachments = request_files.getlist("attachment") + if attachments: + for attachment in attachments: + file_instance = ReimbursementMultipleAttachment() + file_instance.attachment = attachment + file_instance.save() + multiple_attachment_ids.append(file_instance.pk) + + instance = super().save() + instance.other_attachments.add(*multiple_attachment_ids) + + return super().save(**kwargs) + +class TaxBracketSerializer(serializers.ModelSerializer): + class Meta: + fields = '__all__' + model = TaxBracket diff --git a/horilla_api/api_urls/__init__.py b/horilla_api/api_urls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/horilla_api/api_urls/asset/urls.py b/horilla_api/api_urls/asset/urls.py new file mode 100644 index 000000000..2c37fc33d --- /dev/null +++ b/horilla_api/api_urls/asset/urls.py @@ -0,0 +1,13 @@ +from django.urls import re_path, path +from ...api_views.asset.views import * + +urlpatterns = [ + re_path(r'^asset-categories/(?P\d+)?$', AssetCategoryAPIView.as_view(), name='asset-category-detail'), + re_path(r'^asset-lots/(?P\d+)?$', AssetLotAPIView.as_view(), name='asset-lot-detail'), + re_path(r'^assets/(?P\d+)?$', AssetAPIView.as_view(), name='asset-detail'), + re_path(r'^asset-allocations/(?P\d+)?$', AssetAllocationAPIView.as_view(), name='asset-allocation-detail'), + re_path(r'^asset-requests/(?P\d+)?$', AssetRequestAPIView.as_view(), name='asset-request-detail'), + path('asset-return/', AssetReturnAPIView.as_view(), name='asset-return'), + path('asset-reject/', AssetRejectAPIView.as_view(), name='asset-reject'), + path('asset-approve/', AssetApproveAPIView.as_view(), name='asset-approve'), +] \ No newline at end of file diff --git a/horilla_api/api_urls/attendance/urls.py b/horilla_api/api_urls/attendance/urls.py new file mode 100644 index 000000000..f90697b38 --- /dev/null +++ b/horilla_api/api_urls/attendance/urls.py @@ -0,0 +1,53 @@ +""" +horilla_api/urls/attendance/urls.py +""" + +from django.urls import path +from horilla_api.api_views.attendance.views import * +from horilla_api.api_views.attendance.permission_views import AttendancePermissionCheck + +urlpatterns = [ + path("clock-in/", ClockInAPIView.as_view(), name="check-in"), + path("clock-out/", ClockOutAPIView.as_view(), name="check-out"), + path("attendance/", AttendanceView.as_view(), name="attendance-list"), + path("attendance/", AttendanceView.as_view(), name="attendance-detail"), + path( + "attendance/list/", AttendanceView.as_view(), name="attendance-list" + ), + path("attendance-validate/", ValidateAttendanceView.as_view(), name=""), + path( + "attendance-request/", + AttendanceRequestView.as_view(), + name="attendance-request-view", + ), + path( + "attendance-request/", + AttendanceRequestView.as_view(), + name="attendance-request-view", + ), + path( + "attendance-request-approve/", + AttendanceRequestApproveView.as_view(), + name="", + ), + path( + "attendance-request-cancel/", + AttendanceRequestCancelView.as_view(), + name="", + ), + path("overtime-approve/", OvertimeApproveView.as_view(), name=""), + path( + "attendance-hour-account//", AttendanceOverTimeView.as_view(), name="" + ), + path("attendance-hour-account/", AttendanceOverTimeView.as_view(), name=""), + path("late-come-early-out-view/", LateComeEarlyOutView.as_view(), name=""), + path("attendance-activity/", AttendanceActivityView.as_view(), name=""), + path("today-attendance/", TodayAttendance.as_view(), name=""), + path("offline-employees/count/", OfflineEmployeesCountView.as_view(), name=""), + path("offline-employees/list/", OfflineEmployeesListView.as_view(), name=""), + path("permission-check/attendance", AttendancePermissionCheck.as_view()), + path("checking-in", CheckingStatus.as_view()), + path("offline-employee-mail-send", OfflineEmployeeMailsend.as_view()), + path("converted-mail-template", ConvertedMailTemplateConvert.as_view()), + path("mail-templates", MailTemplateView.as_view()), +] diff --git a/horilla_api/api_urls/auth/urls.py b/horilla_api/api_urls/auth/urls.py new file mode 100644 index 000000000..6dcb9ab14 --- /dev/null +++ b/horilla_api/api_urls/auth/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from ...api_views.auth.views import LoginAPIView + + +urlpatterns = [ + path('login/', LoginAPIView.as_view()) +] \ No newline at end of file diff --git a/horilla_api/api_urls/base/urls.py b/horilla_api/api_urls/base/urls.py new file mode 100644 index 000000000..8a06f85d1 --- /dev/null +++ b/horilla_api/api_urls/base/urls.py @@ -0,0 +1,66 @@ +from django.urls import re_path,path +from ...api_views.base import views + +urlpatterns = [ + path('job-positions/', views.JobPositionView.as_view(), name="job_position_detail"), + path('job-positions//', views.JobPositionView.as_view(), name="job_position_detail_with_pk"), + path('job-roles/', views.JobRoleView.as_view(), name="job_roles_details"), + path('job-roles//', views.JobRoleView.as_view(), name="job_roles_details_with_pk"), + path('companies/', views.CompanyView.as_view(), name="companies_detail"), + path('companies//', views.CompanyView.as_view(), name="companies_detail_with_pk"), + path('departments/', views.DepartmentView.as_view(), name="department_detail"), + path('departments//', views.DepartmentView.as_view(), name="department_detail_with_pk"), + path('worktypes/', views.WorkTypeView.as_view(), name="worktype_detail"), + path('worktypes//', views.WorkTypeView.as_view(), name="worktype_detail_with_pk"), + path('rotating-worktypes/', views.RotatingWorkTypeView.as_view(), name="rotating_worktypes_detail"), + path('rotating-worktypes//', views.RotatingWorkTypeView.as_view(), name="rotating_worktypes_detail_with_pk"), + + path('rotating-worktype-assigns/', views.RotatingWorkTypeAssignView.as_view(), name='rotating_worktype_assign_detail'), + path('individual-rotating-worktypes/', views.IndividualRotatingWorktypesView.as_view(), name='individual-worktype'), + path('individual-rotating-worktypes/', views.IndividualRotatingWorktypesView.as_view(), name='individual-worktype'), + + path('individual-worktype-request/', views.IndividualWorkTypeRequestView.as_view(), name='individual-worktype-request'), + path('individual-worktype-request/', views.IndividualWorkTypeRequestView.as_view(), name='individual-worktype-request'), + path('rotating-worktype-assigns//', views.RotatingWorkTypeAssignView.as_view(), name='rotating_worktype_assign_detail_with_pk'), + path('employee-shift/', views.EmployeeShiftView.as_view(), name='employee_shift_detail'), + path('employee-shift//', views.EmployeeShiftView.as_view(), name='employee_shift_detail_with_pk'), + path('employee-shift-schedules/', views.EmployeeShiftScheduleView.as_view(), name='employee_shift_schedule_detail'), + path('employee-shift-schedules//', views.EmployeeShiftScheduleView.as_view(), name='employee_shift_schedule_detail_with_pk'), + path('rotating-shifts/', views.RotatingShiftView.as_view(), name='rotating_shifts_detail'), + path('rotating-shifts//', views.RotatingShiftView.as_view(), name='rotating_shifts_detail_with_pk'), + path('rotating-shift-assigns/', views.RotatingShiftAssignView.as_view(), name='rotating_shift_assigns_detail'), + path('rotating-shift-assigns//', views.RotatingShiftAssignView.as_view(), name='rotating_shift_assigns_detail_with_pk'), + path('individual-rotating-shifts/', views.IndividualRotatingShiftView.as_view(), name='individual-worktype-request'), + path('individual-rotating-shifts/', views.IndividualRotatingShiftView.as_view(), name='individual-worktype-request'), + + path('worktype-requests/', views.WorkTypeRequestView.as_view(), name='worktype_requests_detail'), + path('worktype-requests//', views.WorkTypeRequestView.as_view(), name='worktype_requests_detail_with_pk'), + path('worktype-requests-cancel//', views.WorkTypeRequestCancelView.as_view(), name='worktype_requests_detail_with_pk'), + path('worktype-requests-approve//', views.WorkRequestApproveView.as_view(), name='worktype_requests_detail_with_pk'), + path('shift-requests/', views.ShiftRequestView.as_view(), name='shift_requests_detail'), + path('shift-requests//', views.ShiftRequestView.as_view(), name='shift_requests_detail_with_pk'), + path('individual-shift-request/', views.IndividualShiftRequestView.as_view(), name='individual-worktype-request'), + path('individual-shift-request/', views.IndividualShiftRequestView.as_view(), name='individual-worktype-request'), + + path('shift-request-approve/', views.ShiftRequestApproveView.as_view(), name='shift-requests-approve'), + path('shift-request-bulk-approve', views.ShiftRequestBulkApproveView.as_view(), name='shift-request-bulk-approve'), + path('shift-request-cancel/', views.ShiftRequestCancelView.as_view(), name='shift-request-cancel'), + path('shift-request-bulk-cancel', views.ShiftRequestBulkCancelView.as_view(), name='shift-request-bulk-cancel'), + path('shift-request-delete/', views.ShiftRequestDeleteView.as_view(), name='shift-request-delete'), + path('shift-request-bulk-delete', views.ShiftRequestDeleteView.as_view(), name='shift-request-bulk-delete'), + path('shift-request-export', views.ShiftRequestExportView.as_view(), name='shift-request-export'), + path('shift-request-allocation/', views.ShiftRequestAllocationView.as_view(), name='shift-request-allocation'), + path('work-type-request-export', views.WorkTypeRequestExport.as_view(), name='work-type-request-export'), + path('rotating-shift-assign-export', views.RotatingShiftAssignExport.as_view(), name='rotating-shift-assigns-export'), + path('rotating-shift-assign-bulk-archive/', views.RotatingShiftAssignBulkArchive.as_view(), name='rotating-shift-assigns-archive'), + path('rotating-shift-assign-bulk-delete', views.RotatingShiftAssignBulkDelete.as_view(), name='rotating-shift-assigns-bulk-delete'), + path('disciplinary-action-type/', views.ActiontypeView.as_view(), name='disciplinary-action-type'), + path('disciplinary-action-type//', views.ActiontypeView.as_view(), name='disciplinary-action-type'), + path('rotating-worktype-create-permission-check/', views.RotatingWorKTypePermissionCheck.as_view(), name='rotating-worktype-create-permission-check'), + path('rotating-shift-create-permission-check/', views.RotatingShiftPermissionCheck.as_view(), name='rotating-shift-create-permission-check'), + path('shift-request-approve-permission-check', views.ShiftRequestApprovePermissionCheck.as_view(), name='rotating-worktype-create-permission-check'), + path('worktype-request-approve-permission-check', views.WorktypeRequestApprovePermissionCheck.as_view(), name='rotating-shift-create-permission-check'), + path('employee-tab-permission-check', views.EmployeeTabPermissionCheck.as_view(), name='rotating-shift-create-permission-check'), + + +] diff --git a/horilla_api/api_urls/employee/urls.py b/horilla_api/api_urls/employee/urls.py new file mode 100644 index 000000000..1fbe25ddc --- /dev/null +++ b/horilla_api/api_urls/employee/urls.py @@ -0,0 +1,44 @@ +from django.urls import path + +from ...api_views.employee import views as views + + +urlpatterns = [ + path('employees/', views.EmployeeAPIView.as_view(), name='employees-list'), + path('employees//', views.EmployeeAPIView.as_view(), name='employee-detail'), + + path('employee-type/', views.EmployeeTypeAPIView.as_view(), name='employees'), + path('employee-type/', views.EmployeeTypeAPIView.as_view(), name='employees'), + path('list/employees/', views.EmployeeListAPIView.as_view(), name='employee-list-detailed'), # Alternative endpoint for listing employees + + path('employee-bank-details/', views.EmployeeBankDetailsAPIView.as_view(), name='employee-bank-details-list'), + path('employee-bank-details//', views.EmployeeBankDetailsAPIView.as_view(), name='employee-bank-details-detail'), + + path('employee-work-information/', views.EmployeeWorkInformationAPIView.as_view(), name='employee-work-information-list'), + path('employee-work-information//', views.EmployeeWorkInformationAPIView.as_view(), name='employee-work-information-detail'), + + path('employee-work-info-export/', views.EmployeeWorkInfoExportView.as_view(), name='employee-work-info-export'), + path('employee-work-info-import/', views.EmployeeWorkInfoImportView.as_view(), name='employee-work-info-import'), + + path('employee-bulk-update/', views.EmployeeBulkUpdateView.as_view(), name='employee-bulk-update'), + + path('disciplinary-action/', views.DisciplinaryActionAPIView.as_view(), name='disciplinary-action-list'), + path('disciplinary-action//', views.DisciplinaryActionAPIView.as_view(), name='disciplinary-action-detail'), + + path('policies/', views.PolicyAPIView.as_view(), name='policy-list'), + path('policies//', views.PolicyAPIView.as_view(), name='policy-detail'), + + path('document-request/', views.DocumentRequestAPIView.as_view(), name='document-request-list'), + path('document-request//', views.DocumentRequestAPIView.as_view(), name='document-request-detail'), + + path('document-bulk-approve-reject/', views.DocumentBulkApproveRejectAPIView.as_view(), name='document-bulk-approve-reject'), + + path('document-request-approve-reject///', views.DocumentRequestApproveRejectView.as_view(), name='document-request-approve-reject'), + + path('documents/', views.DocumentAPIView.as_view(), name='document-list'), + path('documents//', views.DocumentAPIView.as_view(), name='document-detail'), + path("employee-bulk- archive//", views.EmployeeBulkArchiveView.as_view(), name='employee-bulk-archive'), + path("employee-archive///", views.EmployeeArchiveView.as_view(), name='employee-archive'), + path("employee-selector/", views.EmployeeSelectorView.as_view(), name='employee-selector'), + path("manager-check/", views.ReportingManagerCheck.as_view(), name='manager-check'), +] \ No newline at end of file diff --git a/horilla_api/api_urls/leave/urls.py b/horilla_api/api_urls/leave/urls.py new file mode 100644 index 000000000..7c953b994 --- /dev/null +++ b/horilla_api/api_urls/leave/urls.py @@ -0,0 +1,47 @@ +""" +horilla_api/urls/leave/urls.py +""" + +from django.urls import path +from horilla_api.api_views.leave.views import * + +urlpatterns = [ + path("available-leave/", EmployeeAvailableLeaveGetAPIView.as_view()), + path("user-request/", EmployeeLeaveRequestGetCreateAPIView.as_view()), + path("user-request//", EmployeeLeaveRequestUpdateDeleteAPIView.as_view()), + path("leave-type/", LeaveTypeGetCreateAPIView.as_view()), + path("leave-type//", LeaveTypeGetUpdateDeleteAPIView.as_view()), + path("allocation-request/", LeaveAllocationRequestGetCreateAPIView.as_view()), + path( + "allocation-request//", + LeaveAllocationRequestGetUpdateDeleteAPIView.as_view(), + ), + path("assign-leave/", AssignLeaveGetCreateAPIView.as_view()), + path("assign-leave//", AssignLeaveGetUpdateDeleteAPIView.as_view()), + path("request/", LeaveRequestGetCreateAPIView.as_view()), + path("request//", LeaveRequestGetUpdateDeleteAPIView.as_view()), + path("company-leave/", CompanyLeaveGetCreateAPIView.as_view()), + path("company-leave//", CompanyLeaveGetUpdateDeleteAPIView.as_view()), + path("holiday/", HolidayGetCreateAPIView.as_view()), + path("holiday//", HolidayGetUpdateDeleteAPIView.as_view()), + path("approve//", LeaveRequestApproveAPIView.as_view()), + path("reject//", LeaveRequestRejectAPIView.as_view()), + path("cancel//", LeaveRequestCancelAPIView.as_view()), + path("allocation-approve//", LeaveAllocationApproveAPIView.as_view()), + path("allocation-reject//", LeaveAllocationRequestRejectAPIView.as_view()), + path("request-bulk-action/", LeaveRequestBulkApproveDeleteAPIview.as_view()), + path("user-allocation-request/", EmployeeLeaveAllocationGetCreateAPIView.as_view()), + path( + "user-allocation-request//", + EmployeeLeaveAllocationUpdateDeleteAPIView.as_view(), + ), + path("status/", LeaveRequestedApprovedCountAPIView.as_view()), + path( + "employee-leave-type//", EmployeeAvailableLeaveTypeGetAPIView.as_view() + ), + path("check-type/", LeaveTypeGetPermissionCheckAPIView.as_view()), + path("check-allocation/", LeaveAllocationGetPermissionCheckAPIView.as_view()), + path("check-request/", LeaveRequestGetPermissionCheckAPIView.as_view()), + path("check-assign/", LeaveAssignGetPermissionCheckAPIView.as_view()), + path("check-perm/", LeavePermissionCheckAPIView.as_view()), +] diff --git a/horilla_api/api_urls/notifications/urls.py b/horilla_api/api_urls/notifications/urls.py new file mode 100644 index 000000000..56c0da88c --- /dev/null +++ b/horilla_api/api_urls/notifications/urls.py @@ -0,0 +1,11 @@ +from django.urls import re_path, path +from ...api_views.notifications import views + + +urlpatterns = [ + path("notifications/list/", views.NotificationView.as_view()), + path("notifications//", views.NotificationReadDelView.as_view()), + path("notifications/bulk-delete-unread/", views.NotificationBulkDelUnreadMessageView.as_view()), + path("notifications/bulk-read/", views.NotificationBulkReadDelView.as_view()), + path("notifications/bulk-delete/", views.NotificationBulkReadDelView.as_view()), +] diff --git a/horilla_api/api_urls/payroll/serializers.py b/horilla_api/api_urls/payroll/serializers.py new file mode 100644 index 000000000..db53e0c85 --- /dev/null +++ b/horilla_api/api_urls/payroll/serializers.py @@ -0,0 +1,279 @@ +from rest_framework import serializers +from employee.models import BonusPoint, Employee +from leave.models import LeaveType +from payroll.models.models import Allowance, Contract, Deduction, LoanAccount, MultipleCondition, Payslip, Reimbursement, ReimbursementMultipleAttachment +from payroll.models.tax_models import TaxBracket + + +class PayslipSerializer(serializers.ModelSerializer): + employee_first_name = serializers.CharField( + source="employee_id.employee_first_name", read_only=True) + employee_last_name = serializers.CharField( + source="employee_id.employee_last_name", read_only=True) + shift_name = serializers.CharField( + source="shift_id.employee_shift", read_only=True) + badge_id = serializers.CharField( + source="employee_id.badge_id", read_only=True) + employee_profile_url = serializers.SerializerMethodField(read_only=True) + department_name = serializers.CharField( + source='employee_id.employee_work_info.department_id.department', read_only=True) + bank_account_check_number = serializers.CharField( + source='employee_id.employee_bank_details.account_number', read_only=True) + + + class Meta: + model = Payslip + fields = '__all__' + # exclude = ['reference', + # 'sent_to_employee', + # 'installment_ids', 'created_at'] + + + def get_employee_profile_url(self, obj): + try: + employee_profile = obj.employee_id.employee_profile + return employee_profile.url + except: + return None + +class ContractSerializer(serializers.ModelSerializer): + employee_first_name = serializers.CharField( + source="employee_id.employee_first_name", read_only=True) + employee_last_name = serializers.CharField( + source="employee_id.employee_last_name", read_only=True) + shift_name = serializers.CharField( + source="shift_id.employee_shift", read_only=True) + badge_id = serializers.CharField( + source="employee_id.badge_id", read_only=True) + employee_profile_url = serializers.SerializerMethodField(read_only=True) + + job_position_name = serializers.CharField( + source='job_position_id.job_position', read_only=True) + job_role_name = serializers.CharField( + source='job_role_id.job_role', read_only=True) + department_name = serializers.CharField( + source='department_id.department', read_only=True) + shift_name = serializers.CharField( + source='shift_id.employee_shift', read_only=True) + work_type_name = serializers.CharField( + source='work_type_id.work_type', read_only=True) + def get_employee_profile_url(self, obj): + try: + employee_profile = obj.employee_id.employee_profile + return employee_profile.url + except: + return None + + class Meta: + model = Contract + fields = '__all__' + + +class MultipleConditionSerializer(serializers.ModelSerializer): + class Meta: + model = MultipleCondition + fields = '__all__' + + +class AllowanceSerializer(serializers.ModelSerializer): + specific_employees = serializers.PrimaryKeyRelatedField( + queryset=Employee.objects.all(), many=True, required=False + ) + exclude_employees = serializers.PrimaryKeyRelatedField( + queryset=Employee.objects.all(), many=True, required=False + ) + other_conditions = serializers.PrimaryKeyRelatedField( + queryset=MultipleCondition.objects.all(), many=True, required=False + ) + + class Meta: + model = Allowance + fields = '__all__' + read_only_fields = ['id', 'company_id', 'only_show_under_employee', 'is_loan'] + + def create(self, validated_data): + specific_employees = validated_data.pop('specific_employees', []) + exclude_employees = validated_data.pop('exclude_employees', []) + other_conditions = validated_data.pop('other_conditions', []) + + allowance = Allowance.objects.create(**validated_data) + + # Set the ManyToMany relationships after the instance is created + allowance.specific_employees.set(specific_employees) + allowance.exclude_employees.set(exclude_employees) + allowance.other_conditions.set(other_conditions) + + return allowance + + def validate(self, data): + is_fixed = data.get('is_fixed') + amount = data.get('amount') + based_on = data.get('based_on') + per_attendance_fixed_amount = data.get('per_attendance_fixed_amount') + shift_id = data.get('shift_id') + work_type_id = data.get('work_type_id') + is_condition_based = data.get('is_condition_based') + field = data.get('field') + condition = data.get('condition') + value = data.get('value') + has_max_limit = data.get('has_max_limit') + maximum_amount = data.get('maximum_amount') + + if is_fixed and (amount is None or amount < 0): + raise serializers.ValidationError("If 'is_fixed' is True, 'amount' must be a positive number.") + + if not is_fixed and not based_on: + raise serializers.ValidationError("If 'is_fixed' is False, 'based_on' is required.") + + if based_on == "attendance" and not per_attendance_fixed_amount: + raise serializers.ValidationError("If 'based_on' is 'attendance', 'per_attendance_fixed_amount' is required.") + + if based_on == "shift_id" and not shift_id: + raise serializers.ValidationError("If 'based_on' is 'shift_id', 'shift_id' is required.") + + if based_on == "work_type_id" and not work_type_id: + raise serializers.ValidationError("If 'based_on' is 'work_type_id', 'work_type_id' is required.") + + if is_condition_based and (not field or not value or not condition): + raise serializers.ValidationError("If 'is_condition_based' is True, 'field', 'value', and 'condition' are required.") + + if has_max_limit and maximum_amount is None: + raise serializers.ValidationError("If 'has_max_limit' is True, 'maximum_amount' is required.") + + return data +class DeductionSerializer(serializers.ModelSerializer): + class Meta: + model = Deduction + fields = '__all__' + + +class LoanAccountSerializer(serializers.ModelSerializer): + employee_profile_url = serializers.SerializerMethodField(read_only=True) + employee_full_name = serializers.CharField(source='employee_id.get_full_name',read_only=True) + badge_id = serializers.CharField(source='employee_id.badge_id',read_only=True) + job_position_name = serializers.CharField(source='employee_id.get_job_position',read_only=True) + class Meta: + model = LoanAccount + fields = '__all__' + + def get_employee_profile_url(self, obj): + try: + employee_profile = obj.employee_id.employee_profile + return employee_profile.url + except: + return None + + +class ReimbursementSerializer(serializers.ModelSerializer): + other_attachements = serializers.SerializerMethodField() + leave_type_name = serializers.CharField(source='leave_type_id.name',read_only=True) + employee_profile_url = serializers.SerializerMethodField(read_only=True) + badge_id = serializers.CharField(source='employee_id.badge_id') + employee_full_name = serializers.CharField(source='employee_id.get_full_name') + + def get_employee_profile_url(self, obj): + try: + employee_profile = obj.employee_id.employee_profile + return employee_profile.url + except: + return None + + class Meta: + model = Reimbursement + fields = '__all__' + + def get_other_attachements(self, obj): + attachments = [] + for attachment in obj.other_attachments.all(): + try: + attachments.append(attachment.attachment.url) + except : + pass + return attachments + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + exclude_fields = [] + # Get type from data or instance + instance_type = getattr(self.instance, 'type', None) + + if instance_type == "reimbursement": + exclude_fields.extend( + ["leave_type_id", "cfd_to_encash", "ad_to_encash", "bonus_to_encash"]) + elif instance_type == "leave_encashment": + exclude_fields.extend(["attachment", "amount", "bonus_to_encash"]) + elif instance_type == "bonus_encashment": + exclude_fields.extend( + ["attachment", "amount", "leave_type_id", "cfd_to_encash", "ad_to_encash"]) + + # Remove excluded fields from serializer fields + for field in exclude_fields: + self.fields.pop(field, None) + + def get_encashable_leaves(self, employee): + leaves = LeaveType.objects.filter( + employee_available_leave__employee_id=employee, + employee_available_leave__total_leave_days__gte=1, + is_encashable=True, + ) + return leaves + + def validate(self, data): + try: + employee_id = self.instance.employee_id + type = self.instance.type + leave_type_id = self.instance.leave_type_id + except: + employee_id = data["employee_id"] + type = data["type"] + leave_type_id = data["leave_type_id"] if data.get("leave_type_id",None) else None + + available_points = BonusPoint.objects.filter( + employee_id=employee_id + ).first() + if type == "bonus_encashment": + try: + bonus_to_encash = self.instance.bonus_to_encash + except: + bonus_to_encash = data["bonus_to_encash"] + + if available_points.points < bonus_to_encash: + raise serializers.ValidationError( + {"bonus_to_encash": "Not enough bonus points to redeem"} + ) + if bonus_to_encash <= 0: + raise serializers.ValidationError( + { + "bonus_to_encash": "Points must be greater than zero to redeem." + } + ) + if type == "leave_encashment": + leave_type_id = leave_type_id + encashable_leaves = self.get_encashable_leaves(employee_id) + if (leave_type_id is None) or (leave_type_id not in encashable_leaves): + raise serializers.ValidationError( + {"leave_type_id": "This leave type is not encashable"} + ) + + return data + + def save(self, **kwargs): + multiple_attachment_ids = [] + request_files = self.context['request'].FILES + attachments = request_files.getlist("attachment") + if attachments: + for attachment in attachments: + file_instance = ReimbursementMultipleAttachment() + file_instance.attachment = attachment + file_instance.save() + multiple_attachment_ids.append(file_instance.pk) + + instance = super().save() + instance.other_attachments.add(*multiple_attachment_ids) + + return super().save(**kwargs) + +class TaxBracketSerializer(serializers.ModelSerializer): + class Meta: + fields = '__all__' + model = TaxBracket diff --git a/horilla_api/api_urls/payroll/urls.py b/horilla_api/api_urls/payroll/urls.py new file mode 100644 index 000000000..1f3c46d02 --- /dev/null +++ b/horilla_api/api_urls/payroll/urls.py @@ -0,0 +1,23 @@ +from django.urls import path +from ...api_views.payroll.views import * + + +urlpatterns = [ + path('contract/',ContractView.as_view(),), + path('contract/',ContractView.as_view(),), + path('payslip/', PayslipView.as_view(), name=''), + path('payslip/', PayslipView.as_view(), name=''), + path('payslip-download/', PayslipDownloadView.as_view(), name=''), + path('payslip-send-mail/', PayslipSendMailView.as_view(), name=''), + path('loan-account/', LoanAccountView.as_view(), name=''), + path('loan-account/', LoanAccountView.as_view(), name=''), + path('reimbusement/', ReimbursementView.as_view(), name=''), + path('reimbusement/', ReimbursementView.as_view(), name=''), + path('reimbusement-approve-reject/', ReimbusementApproveRejectView.as_view(), name=''), + path('tax-bracket/', TaxBracketView.as_view(), name=''), + path('tax-bracket/', TaxBracketView.as_view(), name=''), + path('allowance', AllowanceView.as_view(), name=''), + path('allowance/', AllowanceView.as_view(), name=''), + path('deduction', DeductionView.as_view(), name=''), + path('deduction/', DeductionView.as_view(), name=''), +] diff --git a/horilla_api/api_views/asset/views.py b/horilla_api/api_views/asset/views.py new file mode 100644 index 000000000..c239fb00a --- /dev/null +++ b/horilla_api/api_views/asset/views.py @@ -0,0 +1,301 @@ +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from asset.models import * +from ...api_serializers.asset.serializers import * +from rest_framework.pagination import PageNumberPagination +from rest_framework.permissions import IsAuthenticated +from django_filters.rest_framework import DjangoFilterBackend +from ...api_filters.asset.filters import AssetCategoryFilter +from asset.filters import AssetFilter +from django.http import QueryDict +from datetime import date + +class AssetAPIView(APIView): + permission_classes = [IsAuthenticated] + filter_backends = [DjangoFilterBackend] + filterset_class = AssetFilter + + def get_asset(self, pk): + try: + return Asset.objects.get(pk=pk) + except Asset.DoesNotExist as e: + raise serializers.ValidationError(e) + + def get(self, request, pk=None): + if pk: + asset = self.get_asset(pk) + serializer = AssetSerializer(asset) + return Response(serializer.data) + paginator = PageNumberPagination() + queryset = Asset.objects.all() + filterset = self.filterset_class(request.GET, queryset=queryset) + page = paginator.paginate_queryset(filterset.qs, request) + serializer = AssetGetAllSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + def post(self, request): + serializer = AssetSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def put(self, request, pk): + asset = self.get_asset(pk) + serializer = AssetSerializer(asset, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, pk): + asset = self.get_asset(pk) + asset.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + +class AssetCategoryAPIView(APIView): + permission_classes = [IsAuthenticated] + filter_backends = [DjangoFilterBackend] + filterset_class = AssetCategoryFilter + + def get_asset_category(self, pk): + try: + return AssetCategory.objects.get(pk=pk) + except AssetCategory.DoesNotExist as e: + raise serializers.ValidationError(e) + + def get(self, request, pk=None): + if pk: + asset_category = self.get_asset_category(pk) + serializer = AssetCategorySerializer(asset_category) + return Response(serializer.data) + paginator = PageNumberPagination() + queryset = AssetCategory.objects.all() + filterset = self.filterset_class(request.GET, queryset=queryset) + page = paginator.paginate_queryset(filterset.qs, request) + serializer = AssetCategorySerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + def post(self, request): + serializer = AssetCategorySerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def put(self, request, pk): + asset_category = self.get_asset_category(pk) + serializer = AssetCategorySerializer(asset_category, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, pk): + asset_category = self.get_asset_category(pk) + asset_category.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class AssetLotAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_asset_lot(self, pk): + try: + return AssetLot.objects.get(pk=pk) + except AssetLot.DoesNotExist as e: + raise serializers.ValidationError(e) + + def get(self, request, pk=None): + if pk: + asset_lot = self.get_asset_lot(pk) + serializer = AssetLotSerializer(asset_lot) + return Response(serializer.data) + paginator = PageNumberPagination() + assets = AssetLot.objects.all() + page = paginator.paginate_queryset(assets, request) + serializer = AssetLotSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + def post(self, request): + serializer = AssetLotSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def put(self, request, pk): + asset_lot = self.get_asset_lot(pk) + serializer = AssetLotSerializer(asset_lot, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, pk): + asset_lot = self.get_asset_lot(pk) + asset_lot.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class AssetAllocationAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_asset_assignment(self, pk): + try: + return AssetAssignment.objects.get(pk=pk) + except AssetAssignment.DoesNotExist as e: + raise serializers.ValidationError(e) + + def get(self, request, pk=None): + if pk: + asset_assignment = self.get_asset_assignment(pk) + serializer = AssetAssignmentGetSerializer(asset_assignment) + return Response(serializer.data) + paginator = PageNumberPagination() + assets = AssetAssignment.objects.all() + page = paginator.paginate_queryset(assets, request) + serializer = AssetAssignmentGetSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + def post(self, request): + serializer = AssetAssignmentSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def put(self, request, pk): + asset_assignment = self.get_asset_assignment(pk) + serializer = AssetAssignmentSerializer( + asset_assignment, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, pk): + asset_assignment = self.get_asset_assignment(pk) + asset_assignment.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class AssetRequestAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_asset_request(self, pk): + try: + return AssetRequest.objects.get(pk=pk) + except AssetRequest.DoesNotExist as e: + raise serializers.ValidationError(e) + + def get(self, request, pk=None): + if pk: + asset_request = self.get_asset_request(pk) + serializer = AssetRequestGetSerializer(asset_request) + return Response(serializer.data) + paginator = PageNumberPagination() + assets = AssetRequest.objects.all().order_by('-id') + page = paginator.paginate_queryset(assets, request) + serializer = AssetRequestGetSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + def post(self, request): + serializer = AssetRequestSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def put(self, request, pk): + asset_request = self.get_asset_request(pk) + serializer = AssetRequestSerializer(asset_request, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, pk): + asset_request = self.get_asset_request(pk) + asset_request.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class AssetRejectAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_asset_request(self, pk): + try: + return AssetRequest.objects.get(pk=pk) + except AssetRequest.DoesNotExist as e: + raise serializers.ValidationError(e) + + def put(self, request, pk): + asset_request = self.get_asset_request(pk) + if asset_request.asset_request_status == "Requested": + asset_request.asset_request_status = 'Rejected' + asset_request.save() + return Response(status=204) + raise serializers.ValidationError({"error":"Access Denied.."}) + + + +class AssetApproveAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_asset_request(self, pk): + try: + return AssetRequest.objects.get(pk=pk) + except AssetRequest.DoesNotExist as e: + raise serializers.ValidationError(e) + + def put(self, request, pk): + asset_request = self.get_asset_request(pk) + if asset_request.asset_request_status == "Requested": + data = request.data + if isinstance(data, QueryDict): + data = data.dict() + data['assigned_to_employee_id'] = asset_request.requested_employee_id.id + data['assigned_by_employee_id'] = request.user.employee_get.id + serializer = AssetApproveSerializer(data=data, context={'asset_request': asset_request}) + if serializer.is_valid(): + serializer.save() + asset_id = Asset.objects.get(id=data['asset_id']) + asset_id.asset_status = "In use" + asset_id.save() + asset_request.asset_request_status = 'Approved' + asset_request.save() + return Response(status=200) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + raise serializers.ValidationError({"error":"Access Denied.."}) + + +class AssetReturnAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_asset_assignment(self, pk): + try: + return AssetAssignment.objects.get(pk=pk) + except AssetAssignment.DoesNotExist as e: + raise serializers.ValidationError(e) + + def put(self, request, pk): + asset_assignment = self.get_asset_assignment(pk) + if request.user.has_perm('app_name.change_mymodel'): + serializer = AssetReturnSerializer(instance=asset_assignment, data=request.data) + if serializer.is_valid(): + images = [ReturnImages.objects.create(image=image) for image in request.data.getlist('image')] + asset_return = serializer.save() + asset_return.return_images.set(images) + if asset_return.return_status == 'Healthy': + Asset.objects.filter(id=pk).update(asset_status='Available') + else: + Asset.objects.filter(id=pk).update(asset_status='Not-Available') + AssetAssignment.objects.filter(id=asset_return.id).update(return_date=date.today()) + return Response(status=200) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + else: + AssetAssignment.objects.filter(id=pk).update(return_request=True) + return Response(status=200) + diff --git a/horilla_api/api_views/attendance/permission_views.py b/horilla_api/api_views/attendance/permission_views.py new file mode 100644 index 000000000..7d1f204fd --- /dev/null +++ b/horilla_api/api_views/attendance/permission_views.py @@ -0,0 +1,23 @@ +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework.permissions import IsAuthenticated +from ...api_decorators.base.decorators import manager_permission_required + + +class AttendancePermissionCheck(APIView): + permission_classes = [IsAuthenticated] + + @manager_permission_required("attendance.view_attendance") + def get(self,request): + return Response(status=200) + + +class AttendancePermissionCheck(APIView): + permission_classes = [IsAuthenticated] + + @manager_permission_required("attendance.view_attendance") + def get(self,request): + + return Response(status=200) + + diff --git a/horilla_api/api_views/attendance/views.py b/horilla_api/api_views/attendance/views.py new file mode 100644 index 000000000..3b24ab615 --- /dev/null +++ b/horilla_api/api_views/attendance/views.py @@ -0,0 +1,917 @@ +from django.db.models import Case, Value, When, F, CharField +from django.http import QueryDict +from attendance.models import AttendanceActivity +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from datetime import date, datetime, timedelta, timezone +from attendance.models import EmployeeShiftDay +from attendance.views.dashboard import ( + find_expected_attendances, + find_late_come, + find_on_time, +) +from attendance.views.views import * +from attendance.views.clock_in_out import * +from django.shortcuts import get_object_or_404 +from rest_framework.response import Response +from attendance.models import Attendance +from base.methods import is_reportingmanager +from ...api_decorators.base.decorators import manager_permission_required +from ...api_methods.base.methods import groupby_queryset, permission_based_queryset +from employee.filters import EmployeeFilter +from horilla_api.api_serializers.attendance.serializers import ( + AttendanceActivitySerializer, + AttendanceLateComeEarlyOutSerializer, + AttendanceOverTimeSerializer, + AttendanceRequestSerializer, + AttendanceSerializer, + MailTemplateSerializer, +) +from rest_framework.pagination import PageNumberPagination +from django.utils.decorators import method_decorator +from django.contrib.auth.decorators import permission_required +from django.conf import settings +from django.core.mail import EmailMessage +from recruitment.models import RecruitmentMailTemplate +from base.backends import ConfiguredEmailBackend +from django import template +from base.methods import generate_pdf + +# Create your views here. + + +def query_dict(data): + query_dict = QueryDict("", mutable=True) + for key, value in data.items(): + if isinstance(value, list): + for item in value: + query_dict.appendlist(key, item) + else: + query_dict.update({key: value}) + return query_dict + + +class ClockInAPIView(APIView): + """ + Allows authenticated employees to clock in, determining the correct shift and attendance date, including handling night shifts. + + Methods: + post(request): Processes and records the clock-in time. + """ + + permission_classes = [IsAuthenticated] + + def post(self, request): + employee, work_info = employee_exists(request) + datetime_now = datetime.now() + if request.__dict__.get("datetime"): + datetime_now = request.datetime + if employee and work_info is not None: + shift = work_info.shift_id + date_today = date.today() + if request.__dict__.get("date"): + date_today = request.date + attendance_date = date_today + day = date_today.strftime("%A").lower() + day = EmployeeShiftDay.objects.get(day=day) + now = datetime.now().strftime("%H:%M") + if request.__dict__.get("time"): + now = request.time.strftime("%H:%M") + now_sec = strtime_seconds(now) + mid_day_sec = strtime_seconds("12:00") + minimum_hour, start_time_sec, end_time_sec = shift_schedule_today( + day=day, shift=shift + ) + if start_time_sec > end_time_sec: + # night shift + # ------------------ + # Night shift in Horilla consider a 24 hours from noon to next day noon, + # the shift day taken today if the attendance clocked in after 12 O clock. + + if mid_day_sec > now_sec: + # Here you need to create attendance for yesterday + + date_yesterday = date_today - timedelta(days=1) + day_yesterday = date_yesterday.strftime("%A").lower() + day_yesterday = EmployeeShiftDay.objects.get(day=day_yesterday) + minimum_hour, start_time_sec, end_time_sec = shift_schedule_today( + day=day_yesterday, shift=shift + ) + attendance_date = date_yesterday + day = day_yesterday + clock_in_attendance_and_activity( + employee=employee, + date_today=date_today, + attendance_date=attendance_date, + day=day, + now=now, + shift=shift, + minimum_hour=minimum_hour, + start_time=start_time_sec, + end_time=end_time_sec, + in_datetime=datetime_now, + ) + return Response({"message": "Clocked-In"}, status=200) + return Response( + { + "error": "You Don't have work information filled or your employee detail neither entered " + } + ) + + +class ClockOutAPIView(APIView): + """ + Allows authenticated employees to clock out, updating the latest attendance record and handling early outs. + + Methods: + post(request): Records the clock-out time. + """ + + permission_classes = [IsAuthenticated] + + def post(self, request): + datetime_now = datetime.now() + if request.__dict__.get("datetime"): + datetime_now = request.datetime + employee, work_info = employee_exists(request) + shift = work_info.shift_id + date_today = date.today() + if request.__dict__.get("date"): + date_today = request.date + day = date_today.strftime("%A").lower() + day = EmployeeShiftDay.objects.get(day=day) + attendance = ( + Attendance.objects.filter(employee_id=employee) + .order_by("id", "attendance_date") + .last() + ) + if attendance is not None: + day = attendance.attendance_day + now = datetime.now().strftime("%H:%M") + if request.__dict__.get("time"): + now = request.time.strftime("%H:%M") + minimum_hour, start_time_sec, end_time_sec = shift_schedule_today( + day=day, shift=shift + ) + early_out_instance = attendance.late_come_early_out.filter(type="early_out") + if not early_out_instance.exists(): + early_out( + attendance=attendance, start_time=start_time_sec, end_time=end_time_sec + ) + + clock_out_attendance_and_activity( + employee=employee, date_today=date_today, now=now, out_datetime=datetime_now + ) + return Response({"message": "Clocked-Out"}, status=200) + + +class AttendanceView(APIView): + """ + Handles CRUD operations for attendance records. + + Methods: + get_queryset(request, type): Returns filtered attendance records. + get(request, pk=None, type=None): Retrieves a specific record or a list of records. + post(request): Creates a new attendance record. + put(request, pk): Updates an existing attendance record. + delete(request, pk): Deletes an attendance record and adjusts related overtime if needed. + """ + + permission_classes = [IsAuthenticated] + filterset_class = AttendanceFilters + + def get_queryset(self, request, type): + if type == "ot": + condition = AttendanceValidationCondition.objects.first() + minot = strtime_seconds("00:30") + if condition is not None: + minot = strtime_seconds(condition.minimum_overtime_to_approve) + queryset = Attendance.objects.filter( + overtime_second__gte=minot, + attendance_validated=True, + ) + + elif type == "validated": + queryset = Attendance.objects.filter(attendance_validated=True) + elif type == "non-validated": + queryset = Attendance.objects.filter(attendance_validated=False) + else: + queryset = Attendance.objects.all() + user = request.user + # checking user level permissions + perm = "attendance.view_attendance" + queryset = permission_based_queryset(user, perm, queryset, user_obj=True) + return queryset + + def get(self, request, pk=None, type=None): + # individual object workflow + if pk: + attendance = get_object_or_404(Attendance, pk=pk) + serializer = AttendanceSerializer(instance=attendance) + return Response(serializer.data, status=200) + # permission based querysete + attendances = self.get_queryset(request, type) + # filtering queryset + attendances_filter_queryset = self.filterset_class( + request.GET, queryset=attendances + ).qs + field_name = request.GET.get("groupby_field", None) + if field_name: + url = request.build_absolute_uri() + return groupby_queryset( + request, url, field_name, attendances_filter_queryset + ) + # pagination workflow + paginater = PageNumberPagination() + page = paginater.paginate_queryset(attendances_filter_queryset, request) + serializer = AttendanceSerializer(page, many=True) + return paginater.get_paginated_response(serializer.data) + + @manager_permission_required("attendance.add_attendance") + def post(self, request): + serializer = AttendanceSerializer(data=request.data) + if serializer.is_valid(): + validated_data = serializer.validated_data + instance = Attendance(**validated_data) + instance.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator( + permission_required("attendance.change_attendance", raise_exception=True) + ) + def put(self, request, pk): + try: + attendance = Attendance.objects.get(id=pk) + except Attendance.DoesNotExist: + return Response({"detail": "Attendance record not found."}, status=404) + + serializer = AttendanceSerializer(instance=attendance, data=request.data) + + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + + # Customize error message for unique constraint + serializer_errors = serializer.errors + if "non_field_errors" in serializer.errors: + unique_error_msg = ( + "The fields employee_id, attendance_date must make a unique set." + ) + if unique_error_msg in serializer.errors["non_field_errors"]: + serializer_errors = { + "non_field_errors": [ + "The employee already has attendance on this date." + ] + } + return Response(serializer_errors, status=400) + + @method_decorator( + permission_required("attendance.delete_attendance", raise_exception=True) + ) + def delete(self, request, pk): + attendance = Attendance.objects.get(id=pk) + month = attendance.attendance_date + month = month.strftime("%B").lower() + overtime = attendance.employee_id.employee_overtime.filter(month=month).last() + if overtime is not None: + if attendance.attendance_overtime_approve: + # Subtract overtime of this attendance + total_overtime = strtime_seconds(overtime.overtime) + attendance_overtime_seconds = strtime_seconds( + attendance.attendance_overtime + ) + if total_overtime > attendance_overtime_seconds: + total_overtime = total_overtime - attendance_overtime_seconds + else: + total_overtime = attendance_overtime_seconds - total_overtime + overtime.overtime = format_time(total_overtime) + overtime.save() + try: + attendance.delete() + return Response({"status", "deleted"}, status=200) + except Exception as error: + return Response({"error:", f"{error}"}, status=400) + else: + try: + attendance.delete() + return Response({"status", "deleted"}, status=200) + except Exception as error: + return Response({"error:", f"{error}"}, status=400) + + +class ValidateAttendanceView(APIView): + """ + Validates an attendance record and sends a notification to the employee. + + Method: + put(request, pk): Marks the attendance as validated and notifies the employee. + """ + + def put(self, request, pk): + attendance = Attendance.objects.filter(id=pk).update(attendance_validated=True) + attendance = Attendance.objects.filter(id=pk).first() + try: + notify.send( + request.user.employee_get, + recipient=attendance.employee_id.employee_user_id, + verb=f"Your attendance for the date {attendance.attendance_date} is validated", + verb_ar=f"تم تحقيق حضورك في تاريخ {attendance.attendance_date}", + verb_de=f"Deine Anwesenheit für das Datum {attendance.attendance_date} ist bestätigt.", + verb_es=f"Se valida tu asistencia para la fecha {attendance.attendance_date}.", + verb_fr=f"Votre présence pour la date {attendance.attendance_date} est validée.", + redirect="/attendance/view-my-attendance", + icon="checkmark", + api_redirect=f"/api/attendance/attendance?employee_id{attendance.employee_id}", + ) + except: + pass + return Response(status=200) + + +class OvertimeApproveView(APIView): + """ + Approves overtime for an attendance record and sends a notification to the employee. + + Method: + put(request, pk): Marks the overtime as approved and notifies the employee. + """ + + def put(self, request, pk): + try: + attendance = Attendance.objects.filter(id=pk).update( + attendance_overtime_approve=True + ) + except Exception as E: + return Response({"error": str(E)}, status=400) + + attendance = Attendance.objects.filter(id=pk).first() + try: + notify.send( + request.user.employee_get, + recipient=attendance.employee_id.employee_user_id, + verb=f"Your {attendance.attendance_date}'s attendance overtime approved.", + verb_ar=f"تمت الموافقة على إضافة ساعات العمل الإضافية لتاريخ {attendance.attendance_date}.", + verb_de=f"Die Überstunden für den {attendance.attendance_date} wurden genehmigt.", + verb_es=f"Se ha aprobado el tiempo extra de asistencia para el {attendance.attendance_date}.", + verb_fr=f"Les heures supplémentaires pour la date {attendance.attendance_date} ont été approuvées.", + redirect="/attendance/attendance-overtime-view", + icon="checkmark", + api_redirect="/api/attendance/attendance-hour-account/", + ) + except: + pass + return Response(status=200) + + +class AttendanceRequestView(APIView): + """ + Handles requests for creating, updating, and viewing attendance records. + + Methods: + get(request, pk=None): Retrieves a specific attendance request by `pk` or a filtered list of requests. + post(request): Creates a new attendance request. + put(request, pk): Updates an existing attendance request. + """ + + serializer_class = AttendanceRequestSerializer + permission_classes = [IsAuthenticated] + + def get(self, request, pk=None): + if pk: + attendance = Attendance.objects.get(id=pk) + serializer = AttendanceRequestSerializer(instance=attendance) + return Response(serializer.data, status=200) + + requests = Attendance.objects.filter( + is_validate_request=True, + ) + requests = filtersubordinates( + request=request, + perm="attendance.view_attendance", + queryset=requests, + ) + requests = requests | Attendance.objects.filter( + employee_id__employee_user_id=request.user, + is_validate_request=True, + ) + request_filtered_queryset = AttendanceFilters(request.GET, requests).qs + field_name = request.GET.get("groupby_field", None) + if field_name: + # groupby workflow + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, request_filtered_queryset) + + pagenation = PageNumberPagination() + page = pagenation.paginate_queryset(request_filtered_queryset, request) + serializer = self.serializer_class(page, many=True) + return pagenation.get_paginated_response(serializer.data) + + def post(self, request): + serializer = AttendanceRequestSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=404) + + @manager_permission_required("attendance.update_attendance") + def put(self, request, pk): + attendance = Attendance.objects.get(id=pk) + serializer = AttendanceRequestSerializer(instance=attendance, data=request.data) + if serializer.is_valid(): + instance = serializer.save() + instance.employee_id = attendance.employee_id + instance.id = attendance.id + if attendance.request_type != "create_request": + attendance.requested_data = json.dumps(instance.serialize()) + attendance.request_description = instance.request_description + # set the user level validation here + attendance.is_validate_request = True + attendance.save() + else: + instance.is_validate_request_approved = False + instance.is_validate_request = True + instance.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=404) + + +class AttendanceRequestApproveView(APIView): + """ + Approves and updates an attendance request. + + Method: + put(request, pk): Approves the attendance request, updates attendance records, and handles related activities. + """ + + @manager_permission_required("attendance.change_attendance") + def put(self, request, pk): + try: + attendance = Attendance.objects.get(id=pk) + prev_attendance_date = attendance.attendance_date + prev_attendance_clock_in_date = attendance.attendance_clock_in_date + prev_attendance_clock_in = attendance.attendance_clock_in + attendance.attendance_validated = True + attendance.is_validate_request_approved = True + attendance.is_validate_request = False + attendance.request_description = None + attendance.save() + if attendance.requested_data is not None: + requested_data = json.loads(attendance.requested_data) + requested_data["attendance_clock_out"] = ( + None + if requested_data["attendance_clock_out"] == "None" + else requested_data["attendance_clock_out"] + ) + requested_data["attendance_clock_out_date"] = ( + None + if requested_data["attendance_clock_out_date"] == "None" + else requested_data["attendance_clock_out_date"] + ) + Attendance.objects.filter(id=pk).update(**requested_data) + # DUE TO AFFECT THE OVERTIME CALCULATION ON SAVE METHOD, SAVE THE INSTANCE ONCE MORE + attendance = Attendance.objects.get(id=pk) + attendance.save() + if ( + attendance.attendance_clock_out is None + or attendance.attendance_clock_out_date is None + ): + attendance.attendance_validated = True + activity = AttendanceActivity.objects.filter( + employee_id=attendance.employee_id, + attendance_date=prev_attendance_date, + clock_in_date=prev_attendance_clock_in_date, + clock_in=prev_attendance_clock_in, + ) + if activity: + activity.update( + employee_id=attendance.employee_id, + attendance_date=attendance.attendance_date, + clock_in_date=attendance.attendance_clock_in_date, + clock_in=attendance.attendance_clock_in, + ) + + else: + AttendanceActivity.objects.create( + employee_id=attendance.employee_id, + attendance_date=attendance.attendance_date, + clock_in_date=attendance.attendance_clock_in_date, + clock_in=attendance.attendance_clock_in, + ) + except Exception as E: + return Response({"error": str(E)}, status=400) + return Response({"status": "approved"}, status=200) + + +class AttendanceRequestCancelView(APIView): + """ + Cancels an attendance request. + + Method: + put(request, pk): Cancels the attendance request, resetting its status and data, and deletes the request if it was a create request. + """ + + def put(self, request, pk): + try: + attendance = Attendance.objects.get(id=pk) + if ( + attendance.employee_id.employee_user_id == request.user + or is_reportingmanager(request) + or request.user.has_perm("attendance.change_attendance") + ): + attendance.is_validate_request_approved = False + attendance.is_validate_request = False + attendance.request_description = None + attendance.requested_data = None + attendance.request_type = None + + attendance.save() + if attendance.request_type == "create_request": + attendance.delete() + except Exception as E: + return Response({"error": str(E)}, status=400) + return Response({"status": "success"}, status=200) + + +class AttendanceOverTimeView(APIView): + """ + Manages CRUD operations for attendance overtime records. + + Methods: + get(request, pk=None): Retrieves a specific overtime record by `pk` or a list of records with filtering and pagination. + post(request): Creates a new overtime record. + put(request, pk): Updates an existing overtime record. + delete(request, pk): Deletes an overtime record. + """ + + permission_classes = [IsAuthenticated] + + def get(self, request, pk=None): + if pk: + attendance_ot = get_object_or_404(AttendanceOverTime, pk=pk) + serializer = AttendanceOverTimeSerializer(attendance_ot) + return Response(serializer.data, status=200) + + filterset_class = AttendanceOverTimeFilter(request.GET) + queryset = filterset_class.qs + self_account = queryset.filter(employee_id__employee_user_id=request.user) + permission_based_queryset = filtersubordinates( + request, queryset, "attendance.view_attendanceovertime" + ) + queryset = permission_based_queryset | self_account + field_name = request.GET.get("groupby_field", None) + if field_name: + # groupby workflow + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, queryset) + + pagenation = PageNumberPagination() + page = pagenation.paginate_queryset(queryset, request) + serializer = AttendanceOverTimeSerializer(page, many=True) + return pagenation.get_paginated_response(serializer.data) + + @manager_permission_required("attendance.add_attendanceovertime") + def post(self, request): + serializer = AttendanceOverTimeSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @manager_permission_required("attendance.change_attendanceovertime") + def put(self, request, pk): + attendance_ot = get_object_or_404(AttendanceOverTime, pk=pk) + serializer = AttendanceOverTimeSerializer( + instance=attendance_ot, data=request.data + ) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator( + permission_required( + "attendance.delete_attendanceovertime", raise_exception=True + ) + ) + def delete(self, request, pk): + attendance = get_object_or_404(AttendanceOverTime, pk=pk) + attendance.delete() + + return Response({"message": "Overtime deleted successfully"}, status=204) + + +class LateComeEarlyOutView(APIView): + """ + Handles retrieval and deletion of late come and early out records. + + Methods: + get(request, pk=None): Retrieves a list of late come and early out records with filtering. + delete(request, pk=None): Deletes a specific late come or early out record by `pk`. + """ + + permission_classes = [IsAuthenticated] + + def get(self, request, pk=None): + data = LateComeEarlyOutFilter(request.GET) + serializer = AttendanceLateComeEarlyOutSerializer(data.qs, many=True) + return Response(serializer.data, status=200) + + def delete(self, request, pk=None): + attendance = get_object_or_404(AttendanceLateComeEarlyOut, pk=pk) + attendance.delete() + return Response({"message": "Attendance deleted successfully"}, status=204) + + +class AttendanceActivityView(APIView): + """ + Retrieves attendance activity records. + + Method: + get(request, pk=None): Retrieves a list of all attendance activity records. + """ + + def get(self, request, pk=None): + data = AttendanceActivity.objects.all() + serializer = AttendanceActivitySerializer(data, many=True) + return Response(serializer.data, status=200) + + +class TodayAttendance(APIView): + """ + Provides the ratio of marked attendances to expected attendances for the current day. + + Method: + get(request): Calculates and returns the attendance ratio for today. + """ + + def get(self, request): + + today = datetime.today() + week_day = today.strftime("%A").lower() + + on_time = find_on_time(request, today=today, week_day=week_day) + late_come = find_late_come(start_date=today) + late_come_obj = len(late_come) + + marked_attendances = late_come_obj + on_time + + expected_attendances = find_expected_attendances(week_day=week_day) + marked_attendances_ratio = 0 + if expected_attendances != 0: + marked_attendances_ratio = ( + f"{(marked_attendances / expected_attendances) * 100:.2f}" + ) + + return Response( + {"marked_attendances_ratio": marked_attendances_ratio}, status=200 + ) + + +class OfflineEmployeesCountView(APIView): + """ + Retrieves the count of active employees who have not clocked in today. + + Method: + get(request): Returns the number of active employees who are not yet clocked in. + """ + + def get(self, request): + count = ( + EmployeeFilter({"not_in_yet": date.today()}) + .qs.exclude(employee_work_info__isnull=True) + .filter(is_active=True) + .count() + ) + return Response({"count": count}, status=200) + + +class OfflineEmployeesListView(APIView): + """ + Lists active employees who have not clocked in today, including their leave status. + + Method: + get(request): Retrieves and paginates a list of employees not clocked in today with their leave status. + """ + + def get(self, request): + queryset = ( + EmployeeFilter({"not_in_yet": date.today()}) + .qs.exclude(employee_work_info__isnull=True) + .filter(is_active=True) + ) + leave_status = self.get_leave_status(queryset) + pagenation = PageNumberPagination() + page = pagenation.paginate_queryset(leave_status, request) + return pagenation.get_paginated_response(page) + + def get_leave_status(self, queryset): + + today = date.today() + queryset = queryset.distinct() + # Annotate each employee with their leave status + employees_with_leave_status = queryset.annotate( + leave_status=Case( + # Define different cases based on leave requests and attendance + When( + leaverequest__start_date__lte=today, + leaverequest__end_date__gte=today, + leaverequest__status="approved", + then=Value("On Leave"), + ), + When( + leaverequest__start_date__lte=today, + leaverequest__end_date__gte=today, + leaverequest__status="requested", + then=Value("Waiting Approval"), + ), + When( + leaverequest__start_date__lte=today, + leaverequest__end_date__gte=today, + then=Value("Canceled / Rejected"), + ), + When( + employee_attendances__attendance_date=today, then=Value("Working") + ), + default=Value("Expected working"), # Default status + output_field=CharField(), + ), + job_position_id=F("employee_work_info__job_position_id"), + ).values( + "employee_first_name", + "employee_last_name", + "leave_status", + "employee_profile", + "id", + "job_position_id", + ) + + for employee in employees_with_leave_status: + + if employee["employee_profile"]: + employee["employee_profile"] = ( + settings.MEDIA_URL + employee["employee_profile"] + ) + return employees_with_leave_status + + +class CheckingStatus(APIView): + """ + Checks and provides the current attendance status for the authenticated user. + + Method: + get(request): Returns the attendance status, duration at work, and clock-in time if available. + """ + + permission_classes = [IsAuthenticated] + + @classmethod + def _format_seconds(cls, seconds): + hours = seconds // 3600 + minutes = (seconds % 3600) // 60 + seconds = seconds % 60 + return f"{hours:02}:{minutes:02}:{seconds:02}" + + def get(self, request): + attendance_activity = ( + AttendanceActivity.objects.filter(employee_id=request.user.employee_get) + .order_by("-id") + .first() + ) + duration = None + work_seconds = request.user.employee_get.get_forecasted_at_work()[ + "forecasted_at_work_seconds" + ] + duration = CheckingStatus._format_seconds(int(work_seconds)) + status = False + clock_in_time = None + + today = datetime.now() + attendance_activity_first = ( + AttendanceActivity.objects.filter( + employee_id=request.user.employee_get, clock_in_date=today + ) + .order_by("in_datetime") + .first() + ) + if attendance_activity: + clock_in_time = attendance_activity_first.clock_in.strftime("%I:%M %p") + if attendance_activity.clock_out_date: + status = False + else: + status = True + return Response( + {"status": status, "duration": duration, "clock_in": clock_in_time}, + status=200, + ) + return Response( + {"status": status, "duration": duration, "clock_in_time": clock_in_time}, + status=200, + ) + + +class MailTemplateView(APIView): + """ + Retrieves a list of recruitment mail templates. + + Method: + get(request): Returns all recruitment mail templates. + """ + + permission_classes = [IsAuthenticated] + + def get(self, request): + instances = RecruitmentMailTemplate.objects.all() + serializer = MailTemplateSerializer(instances, many=True) + return Response(serializer.data, status=200) + + +class ConvertedMailTemplateConvert(APIView): + """ + Renders a recruitment mail template with data from a specified employee. + + Method: + put(request): Renders the mail template body with employee and user data and returns the result. + """ + + permission_classes = [IsAuthenticated] + + def put(self, request): + template_id = request.data.get("template_id", None) + employee_id = request.data.get("employee_id", None) + employee = Employee.objects.filter(id=employee_id).first() + bdy = RecruitmentMailTemplate.objects.filter(id=template_id).first() + template_bdy = template.Template(bdy.body) + context = template.Context( + {"instance": employee, "self": request.user.employee_get} + ) + render_bdy = template_bdy.render(context) + return Response(render_bdy) + + +class OfflineEmployeeMailsend(APIView): + """ + Sends an email with attachments and rendered templates to a specified employee. + + Method: + post(request): Renders email templates with employee and user data, attaches files, and sends the email. + """ + + permission_classes = [IsAuthenticated] + + def post(self, request): + employee_id = request.POST.get("employee_id") + subject = request.POST.get("subject", "") + bdy = request.POST.get("body", "") + other_attachments = request.FILES.getlist("other_attachments") + attachments = [ + (file.name, file.read(), file.content_type) for file in other_attachments + ] + email_backend = ConfiguredEmailBackend() + host = email_backend.dynamic_username + employee = Employee.objects.get(id=employee_id) + template_attachment_ids = request.POST.getlist("template_attachments") + bodys = list( + RecruitmentMailTemplate.objects.filter( + id__in=template_attachment_ids + ).values_list("body", flat=True) + ) + for html in bodys: + # due to not having solid template we first need to pass the context + template_bdy = template.Template(html) + context = template.Context( + {"instance": employee, "self": request.user.employee_get} + ) + render_bdy = template_bdy.render(context) + attachments.append( + ( + "Document", + generate_pdf(render_bdy, {}, path=False, title="Document").content, + "application/pdf", + ) + ) + + template_bdy = template.Template(bdy) + context = template.Context( + {"instance": employee, "self": request.user.employee_get} + ) + render_bdy = template_bdy.render(context) + + email = EmailMessage( + subject, + render_bdy, + host, + [employee.employee_work_info.email], + ) + email.content_subtype = "html" + + email.attachments = attachments + try: + email.send() + if employee.employee_work_info.email: + return Response(f"Mail sent to {employee.get_full_name()}") + else: + return Response(f"Email not set for {employee.get_full_name()}") + except Exception as e: + return Response("Something went wrong") diff --git a/horilla_api/api_views/auth/views.py b/horilla_api/api_views/auth/views.py new file mode 100644 index 000000000..0cb6a05e2 --- /dev/null +++ b/horilla_api/api_views/auth/views.py @@ -0,0 +1,27 @@ +from rest_framework_simplejwt.tokens import RefreshToken +from rest_framework.views import APIView +from rest_framework.response import Response +from django.contrib.auth import authenticate +from ...api_serializers.auth.serializers import GetEmployeeSerializer + + + +class LoginAPIView(APIView): + def post(self, request): + if 'username' and 'password' in request.data.keys(): + username = request.data.get('username') + password = request.data.get('password') + user = authenticate(username=username, password=password) + if user: + refresh = RefreshToken.for_user(user) + employee = user.employee_get + result = { + 'employee' : GetEmployeeSerializer(employee).data, + 'access': str(refresh.access_token), + } + return Response(result, status=200) + else: + return Response({'error': 'Invalid credentials'}, status=401) + else: + return Response({'error':'Please provide Username and Password'}) + diff --git a/horilla_api/api_views/base/views.py b/horilla_api/api_views/base/views.py new file mode 100644 index 000000000..26592a337 --- /dev/null +++ b/horilla_api/api_views/base/views.py @@ -0,0 +1,1282 @@ +from django.http import HttpResponse +from rest_framework.views import APIView +from rest_framework.response import Response +from base.filters import (RotatingShiftAssignFilters, RotatingWorkTypeAssignFilter, ShiftRequestFilter, + WorkTypeRequestFilter) +from base.views import (is_reportingmanger, rotating_work_type_assign_export, + shift_request_export, work_type_request_export) +from ...api_decorators.base.decorators import (check_approval_status, manager_or_owner_permission_required, + manager_permission_required) +from employee.models import Actiontype, Employee +from ...api_methods.base.methods import groupby_queryset, permission_based_queryset +from horilla.decorators import permission_required +from ...api_serializers.base.serializers import (ActiontypeSerializer, CompanySerializer, DepartmentSerializer, + EmployeeShiftScheduleSerializer, EmployeeShiftSerializer, JobPositionSerializer, + JobRoleSerializer, RotatingShiftAssignSerializer, RotatingShiftSerializer, + RotatingWorkTypeAssignSerializer, RotatingWorkTypeSerializer, + ShiftRequestSerializer, WorkTypeRequestSerializer, WorkTypeSerializer) +from base.models import (Department, EmployeeShift, EmployeeShiftSchedule, JobPosition, JobRole, Company, + RotatingShift, RotatingShiftAssign, RotatingWorkType, RotatingWorkTypeAssign, + ShiftRequest, WorkType, WorkTypeRequest) +from rest_framework.pagination import PageNumberPagination +from rest_framework.permissions import IsAuthenticated +from django_filters.rest_framework import DjangoFilterBackend +from django.utils.decorators import method_decorator +from notifications.signals import notify + + +def object_check(cls, pk): + try: + obj = cls.objects.get(id=pk) + return obj + except cls.DoesNotExist: + return None + + +def object_delete(cls, pk): + try: + cls.objects.get(id=pk).delete() + return "", 200 + except Exception as e: + return {"error": str(e)}, 400 + +def individual_permssion_check(request): + employee_id = request.GET.get('employee_id') + employee = Employee.objects.filter(id= employee_id).first() + if request.user.employee_get == employee: + return True + elif employee.employee_work_info.reporting_manager_id == request.user.employee_get: + return True + elif request.user.has_perm('base.view_rotatingworktypeassign'): + return True + return False + + +def _is_reportingmanger(request, instance): + """ + If the instance have employee id field then you can use this method to know the request + user employee is the reporting manager of the instance + """ + manager = request.user.employee_get + try: + employee_work_info_manager = ( + instance.employee_work_info.reporting_manager_id + ) + except Exception: + return HttpResponse("This Employee Dont Have any work information") + return manager == employee_work_info_manager + + +class JobPositionView(APIView): + serializer_class = JobPositionSerializer + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("base.view_jobposition")) + def get(self, request, pk=None): + if pk: + job_position = object_check(JobPosition, pk) + if job_position is None: + return Response({"error": "Job position not found "}, status=404) + serializer = self.serializer_class(job_position) + return Response(serializer.data, status=200) + + job_positions = JobPosition.objects.all() + paginater = PageNumberPagination() + page = paginater.paginate_queryset(job_positions, request) + serializer = self.serializer_class(page, many=True) + return paginater.get_paginated_response(serializer.data) + + @method_decorator(permission_required("base.change_jobposition")) + def put(self, request, pk): + job_position = object_check(JobPosition, pk) + if job_position is None: + return Response({"error": "Job position not found "}, status=404) + serializer = self.serializer_class(job_position, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.add_jobposition")) + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.delete_jobposition")) + def delete(self, request, pk): + job_position = object_check(JobPosition, pk) + if job_position is None: + return Response({"error": "Job position not found "}, status=404) + response, status_code = object_delete(JobPosition, pk) + return Response(response, status=status_code) + + +class DepartmentView(APIView): + serializer_class = DepartmentSerializer + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("base.view_department"), name='dispatch') + def get(self, request, pk=None): + if pk: + department = object_check(Department, pk) + if department is None: + return Response({"error": "Department not found "}, status=404) + serializer = self.serializer_class(department) + return Response(serializer.data, status=200) + + departments = Department.objects.all() + paginator = PageNumberPagination() + page = paginator.paginate_queryset(departments, request) + serializer = self.serializer_class(page, many=True) + return paginator.get_paginated_response(serializer.data) + + @method_decorator(permission_required("base.change_department"), name='dispatch') + def put(self, request, pk): + department = object_check(Department, pk) + if department is None: + return Response({"error": "Department not found "}, status=404) + serializer = self.serializer_class(department, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.add_department"), name='dispatch') + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.delete_department"), name='dispatch') + def delete(self, request, pk): + department = object_check(Department, pk) + if department is None: + return Response({"error": "Department not found "}, status=404) + response, status_code = object_delete(Department, pk) + return Response(response, status=status_code) + + +class JobRoleView(APIView): + serializer_class = JobRoleSerializer + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("base.view_jobrole"), name='dispatch') + def get(self, request, pk=None): + if pk: + job_role = object_check(JobRole, pk) + if job_role is None: + return Response({"error": "Job role not found "}, status=404) + serializer = self.serializer_class(job_role) + return Response(serializer.data, status=200) + + job_roles = JobRole.objects.all() + paginator = PageNumberPagination() + page = paginator.paginate_queryset(job_roles, request) + serializer = self.serializer_class(page, many=True) + return paginator.get_paginated_response(serializer.data) + + @method_decorator(permission_required("base.change_jobrole"), name='dispatch') + def put(self, request, pk): + job_role = object_check(JobRole, pk) + if job_role is None: + return Response({"error": "Job role not found "}, status=404) + serializer = self.serializer_class(job_role, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.add_jobrole"), name='dispatch') + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.delete_jobrole"), name='dispatch') + def delete(self, request, pk): + job_role = object_check(JobRole, pk) + if job_role is None: + return Response({"error": "Job role not found "}, status=404) + response, status_code = object_delete(JobRole, pk) + return Response(response, status=status_code) + + +class CompanyView(APIView): + serializer_class = CompanySerializer + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("base.view_company"), name='dispatch') + def get(self, request, pk=None): + if pk: + company = object_check(Company, pk) + if company is None: + return Response({"error": "Company not found "}, status=404) + serializer = self.serializer_class(company) + return Response(serializer.data, status=200) + + companies = Company.objects.all() + paginator = PageNumberPagination() + page = paginator.paginate_queryset(companies, request) + serializer = self.serializer_class(page, many=True) + return paginator.get_paginated_response(serializer.data) + + @method_decorator(permission_required("base.change_company"), name='dispatch') + def put(self, request, pk): + company = object_check(Company, pk) + if company is None: + return Response({"error": "Company not found "}, status=404) + serializer = self.serializer_class(company, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.add_company"), name='dispatch') + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.delete_company"), name='dispatch') + def delete(self, request, pk): + company = object_check(Company, pk) + if company is None: + return Response({"error": "Company not found "}, status=400) + response, status_code = object_delete(Company, pk) + return Response(response, status=status_code) + + +class WorkTypeView(APIView): + serializer_class = WorkTypeSerializer + permission_classes = [IsAuthenticated] + + def get(self, request, pk=None): + if pk: + work_type = object_check(WorkType, pk) + if work_type is None: + return Response({"error": "WorkType not found"}, status=404) + serializer = self.serializer_class(work_type) + return Response(serializer.data, status=200) + + work_types = WorkType.objects.all() + serializer = self.serializer_class(work_types, many=True) + return Response(serializer.data) + + @method_decorator(permission_required("base.add_worktype"), name='dispatch') + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.change_worktype"), name='dispatch') + def put(self, request, pk): + work_type = object_check(WorkType, pk) + if work_type is None: + return Response({"error": "WorkType not found"}, status=404) + serializer = self.serializer_class(work_type, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.delete_worktype"), name='dispatch') + def delete(self, request, pk): + work_type = object_check(WorkType, pk) + if work_type is None: + return Response({"error": "WorkType not found"}, status=404) + response, status_code = object_delete(WorkType, pk) + return Response(response, status=status_code) + + +class WorkTypeRequestView(APIView): + serializer_class = WorkTypeRequestSerializer + filterset_class = WorkTypeRequestFilter + permission_classes = [IsAuthenticated] + + def get_queryset(self, request): + queryset = WorkTypeRequest.objects.all() + user = request.user + # checking user level permissions + perm = "base.view_worktyperequest" + queryset = permission_based_queryset(user, perm, queryset,user_obj=True) + return queryset + + def get(self, request, pk=None): + # individual object workflow + if pk: + work_type_request = object_check(WorkTypeRequest, pk) + if work_type_request is None: + return Response({"error": "WorkTypeRequest not found"}, status=404) + serializer = self.serializer_class(work_type_request) + return Response(serializer.data, status=200) + # permission based queryset + work_type_requests = self.get_queryset(request) + # filtering queryset + work_type_request_filter_queryset = self.filterset_class( + request.GET, queryset=work_type_requests).qs + # groupby workflow + field_name = request.GET.get("groupby_field", None) + if field_name: + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, work_type_request_filter_queryset) + # pagination workflow + paginater = PageNumberPagination() + page = paginater.paginate_queryset( + work_type_request_filter_queryset, request) + serializer = self.serializer_class(page, many=True) + return paginater.get_paginated_response(serializer.data) + + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + instance = serializer.save() + try: + notify.send( + instance.employee_id, + recipient=( + instance.employee_id.employee_work_info.reporting_manager_id.employee_user_id + ), + verb=f"You have new work type request to \ + validate for {instance.employee_id}", + verb_ar=f"لديك طلب نوع وظيفة جديد للتحقق من \ + {instance.employee_id}", + verb_de=f"Sie haben eine neue Arbeitstypanfrage zur \ + Validierung für {instance.employee_id}", + verb_es=f"Tiene una nueva solicitud de tipo de trabajo para \ + validar para {instance.employee_id}", + verb_fr=f"Vous avez une nouvelle demande de type de travail\ + à valider pour {instance.employee_id}", + icon="information", + redirect=f"/employee/work-type-request-view?id={instance.id}", + api_redirect=f"/api/base/worktype-requests/{instance.id}", + ) + return Response(serializer.data, status=201) + except Exception as E: + return Response(serializer.errors, status=400) + return Response(serializer.errors, status=400) + + @check_approval_status(WorkTypeRequest, "base.change_worktyperequest") + @manager_or_owner_permission_required(WorkTypeRequest, "base.change_worktyperequest") + def put(self, request, pk): + work_type_request = object_check(WorkTypeRequest, pk) + if work_type_request is None: + return Response({"error": "WorkTypeRequest not found"}, status=404) + serializer = self.serializer_class( + work_type_request, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @check_approval_status(WorkTypeRequest, "base.change_worktyperequest") + @manager_or_owner_permission_required(WorkTypeRequest, "base.delete_worktyperequest") + def delete(self, request, pk): + work_type_request = object_check(WorkTypeRequest, pk) + if work_type_request is None: + return Response({"error": "WorkTypeRequest not found"}, status=404) + response, status_code = object_delete(WorkTypeRequest, pk) + return Response(response, status=status_code) + +class WorkTypeRequestCancelView(APIView): + + def put(self,request,pk): + work_type_request = WorkTypeRequest.find(pk) + if ( + is_reportingmanger(request, work_type_request) + or request.user.has_perm("base.cancel_worktyperequest") + or work_type_request.employee_id == request.user.employee_get + and work_type_request.approved == False + ): + work_type_request.canceled = True + work_type_request.approved = False + work_type_request.employee_id.employee_work_info.work_type_id = ( + work_type_request.previous_work_type_id + ) + work_type_request.employee_id.employee_work_info.save() + work_type_request.save() + try: + notify.send( + request.user.employee_get, + recipient=work_type_request.employee_id.employee_user_id, + verb="Your work type request has been rejected.", + verb_ar="تم إلغاء طلب نوع وظيفتك", + verb_de="Ihre Arbeitstypanfrage wurde storniert", + verb_es="Su solicitud de tipo de trabajo ha sido cancelada", + verb_fr="Votre demande de type de travail a été annulée", + redirect=f"/employee/work-type-request-view?id={work_type_request.id}", + icon="close", + api_redirect = '/api/base/worktype-requests//' + ) + except : + pass + return Response(status=200) + + +class WorkRequestApproveView(APIView): + permission_classes =[IsAuthenticated] + def put(self,request,pk): + work_type_request = WorkTypeRequest.find(pk) + if ( + is_reportingmanger(request, work_type_request) + or request.user.has_perm("approve_worktyperequest") + or request.user.has_perm("change_worktyperequest") + and not work_type_request.approved + ): + """ + Here the request will be approved, can send mail right here + """ + if not work_type_request.is_any_work_type_request_exists(): + work_type_request.approved = True + work_type_request.canceled = False + work_type_request.save() + try: + notify.send( + request.user.employee_get, + recipient=work_type_request.employee_id.employee_user_id, + verb="Your work type request has been approved.", + verb_ar="تمت الموافقة على طلب نوع وظيفتك.", + verb_de="Ihre Arbeitstypanfrage wurde genehmigt.", + verb_es="Su solicitud de tipo de trabajo ha sido aprobada.", + verb_fr="Votre demande de type de travail a été approuvée.", + redirect=f"/employee/work-type-request-view?id={work_type_request.id}", + icon="checkmark", + api_redirect ="/api/base/worktype-requests//" + ) + return Response({"status":"approved"}) + except Exception as e: + return Response({"error":str(e)},status=400) + else: + return Response({"error":"You don't have permission"},status=400) + +class WorkTypeRequestExport(APIView): + permission_classes = [IsAuthenticated] + + @manager_permission_required("base.view_worktyperequest") + def get(self, request): + return work_type_request_export(request) + + +class RotatingWorkTypeView(APIView): + + serializer_class = RotatingWorkTypeSerializer + permission_classes = [IsAuthenticated] + + def get_queryset(self, request): + queryset = RotatingWorkType.objects.all() + user = request.user + # checking user level permissions + perm = "base.view_rotatingworktype" + queryset = permission_based_queryset(user, perm, queryset) + return queryset + + def get(self, request, pk=None): + if pk: + rotating_work_type = object_check(RotatingWorkType, pk) + if rotating_work_type is None: + return Response({"error": "RotatingWorkType not found"}, status=404) + serializer = self.serializer_class(rotating_work_type) + return Response(serializer.data, status=200) + + rotating_work_types = self.get_queryset(request) + serializer = self.serializer_class(rotating_work_types, many=True) + return Response(serializer.data, status=200) + + @method_decorator(permission_required("base.add_rotatingworktype"), name='dispatch') + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.change_rotatingworktype"), name='dispatch') + def put(self, request, pk): + rotating_work_type = object_check(RotatingWorkType, pk) + if rotating_work_type is None: + return Response({"error": "RotatingWorkType not found"}, status=404) + serializer = self.serializer_class( + rotating_work_type, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.delete_rotatingworktype"), name='dispatch') + def delete(self, request, pk): + rotating_work_type = object_check(RotatingWorkType, pk) + if rotating_work_type is None: + return Response({"error": "RotatingWorkType not found"}, status=404) + response, status_code = object_delete(RotatingWorkType, pk) + return Response(response, status=status_code) + + +class IndividualRotatingWorktypesView(APIView): + serializer_class = RotatingWorkTypeAssignSerializer + permission_classes = [IsAuthenticated] + + def get(self, request, pk=None): + if individual_permssion_check(request) == False: + return Response({'error':'you have no permssion to view'},status=400) + if pk: + rotating_work_type_assign = object_check( + RotatingWorkTypeAssign, pk) + if rotating_work_type_assign is None: + return Response({"error": "RotatingWorkTypeAssign not found"}, status=404) + serializer = self.serializer_class(rotating_work_type_assign) + return Response(serializer.data, status=200) + employee_id = request.GET.get("employee_id",None) + rotating_work_type_assigns = RotatingWorkTypeAssign.objects.filter(employee_id=employee_id) + pagenation = PageNumberPagination() + page = pagenation.paginate_queryset( + rotating_work_type_assigns, request) + serializer = self.serializer_class( + page, many=True) + return pagenation.get_paginated_response(serializer.data) + +class RotatingWorkTypeAssignView(APIView): + serializer_class = RotatingWorkTypeAssignSerializer + filterset_class = RotatingWorkTypeAssignFilter + permission_classes = [IsAuthenticated] + + @manager_permission_required("base.view_rotatingworktypeassign") + def get(self, request, pk=None): + if pk: + rotating_work_type_assign = object_check( + RotatingWorkTypeAssign, pk) + if rotating_work_type_assign is None: + return Response({"error": "RotatingWorkTypeAssign not found"}, status=404) + serializer = self.serializer_class(rotating_work_type_assign) + return Response(serializer.data, status=200) + rotating_work_type_assigns = RotatingWorkTypeAssign.objects.all() + rotating_work_type_assigns_filter_queryset = self.filterset_class( + request.GET, queryset=rotating_work_type_assigns).qs + field_name = request.GET.get("groupby_field", None) + if field_name: + # groupby workflow + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, rotating_work_type_assigns_filter_queryset) + + pagenation = PageNumberPagination() + page = pagenation.paginate_queryset( + rotating_work_type_assigns_filter_queryset, request) + serializer = self.serializer_class( + page, many=True) + return pagenation.get_paginated_response(serializer.data) + + @manager_permission_required("base.add_rotatingworktypeassign") + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + obj = serializer.save() + try: + users = [employee.employee_user_id for employee in obj] + notify.send( + request.user.employee_get, + recipient=users, + verb="You are added to rotating work type", + verb_ar="تمت إضافتك إلى نوع العمل المتناوب", + verb_de="Sie werden zum rotierenden Arbeitstyp hinzugefügt", + verb_es="Se le agrega al tipo de trabajo rotativo", + verb_fr="Vous êtes ajouté au type de travail rotatif", + icon="infinite", + redirect="/employee/employee-profile/", + api_redirect='' + ) + except: + pass + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @manager_permission_required("base.change_rotatingworktypeassign") + def put(self, request, pk): + rotating_work_type_assign = object_check(RotatingWorkTypeAssign, pk) + if rotating_work_type_assign is None: + return Response({"error": "RotatingWorkTypeAssign not found"}, status=404) + serializer = self.serializer_class( + rotating_work_type_assign, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @manager_permission_required("base.delete_rotatingworktypeassign") + def delete(self, request, pk): + rotating_work_type_assign = object_check(RotatingWorkTypeAssign, pk) + if rotating_work_type_assign is None: + return Response({"error": "RotatingWorkTypeAssign not found"}, status=404) + response, status_code = object_delete(RotatingWorkTypeAssign, pk) + return Response(response, status=status_code) + +class IndividualWorkTypeRequestView(APIView): + serializer_class = WorkTypeRequestSerializer + permission_classes = [IsAuthenticated] + + def get(self, request, pk=None): + if individual_permssion_check(request) == False: + return Response({'error':'you have no permssion to view'},status=400) + + # individual object workflow + if pk: + work_type_request = object_check(WorkTypeRequest, pk) + if work_type_request is None: + return Response({"error": "WorkTypeRequest not found"}, status=404) + serializer = self.serializer_class(work_type_request) + return Response(serializer.data, status=200) + employee_id = request.GET.get('employee_id',None) + work_type_request = WorkTypeRequest.objects.filter(employee_id = employee_id) + paginater = PageNumberPagination() + page = paginater.paginate_queryset( + work_type_request, request) + serializer = self.serializer_class(page, many=True) + return paginater.get_paginated_response(serializer.data) + + +class EmployeeShiftView(APIView): + serializer_class = EmployeeShiftSerializer + permission_classes = [IsAuthenticated] + + def get(self, request, pk=None): + if pk: + employee_shift = object_check(EmployeeShift, pk) + if employee_shift is None: + return Response({"error": "EmployeeShift not found"}, status=404) + serializer = self.serializer_class(employee_shift) + return Response(serializer.data, status=200) + + employee_shifts = EmployeeShift.objects.all() + serializer = self.serializer_class(employee_shifts, many=True) + return Response(serializer.data, status=200) + + @method_decorator(permission_required("base.add_employeeshift"), name='dispatch') + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.change_employeeshift"), name='dispatch') + def put(self, request, pk): + employee_shift = object_check(EmployeeShift, pk) + if employee_shift is None: + return Response({"error": "EmployeeShift not found"}, status=404) + serializer = self.serializer_class(employee_shift, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.delete_employeeshift"), name='dispatch') + def delete(self, request, pk): + employee_shift = object_check(EmployeeShift, pk) + if employee_shift is None: + return Response({"error": "EmployeeShift not found"}, status=404) + response, status_code = object_delete(EmployeeShift, pk) + return Response(response, status=status_code) + + +class EmployeeShiftScheduleView(APIView): + serializer_class = EmployeeShiftScheduleSerializer + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("base.view_employeeshiftschedule"), name='dispatch') + def get(self, request, pk=None): + if pk: + employee_shift_schedule = object_check(EmployeeShiftSchedule, pk) + if employee_shift_schedule is None: + return Response({"error": "EmployeeShiftSchedule not found"}, status=404) + serializer = self.serializer_class(employee_shift_schedule) + return Response(serializer.data, status=200) + + employee_shift_schedules = EmployeeShiftSchedule.objects.all() + serializer = self.serializer_class(employee_shift_schedules, many=True) + return Response(serializer.data, status=200) + + @method_decorator(permission_required("base.add_employeeshiftschedule"), name='dispatch') + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.change_employeeshiftschedule"), name='dispatch') + def put(self, request, pk): + employee_shift_schedule = object_check(EmployeeShiftSchedule, pk) + if employee_shift_schedule is None: + return Response({"error": "EmployeeShiftSchedule not found"}, status=404) + serializer = self.serializer_class( + employee_shift_schedule, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.delete_employeeshiftschedule"), name='dispatch') + def delete(self, request, pk): + employee_shift_schedule = object_check(EmployeeShiftSchedule, pk) + if employee_shift_schedule is None: + return Response({"error": "EmployeeShiftSchedule not found"}, status=404) + response, status_code = object_delete(EmployeeShiftSchedule, pk) + return Response(response, status=status_code) + + +class RotatingShiftView(APIView): + serializer_class = RotatingShiftSerializer + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("base.view_rotatingshift"), name='dispatch') + def get(self, request, pk=None): + + if pk: + rotating_shift = object_check(RotatingShift, pk) + if rotating_shift is None: + return Response({"error": "RotatingShift not found"}, status=404) + serializer = self.serializer_class(rotating_shift) + return Response(serializer.data, status=200) + + employee_id = request.GET.get('employee_id') # Get the employee_id from query parameters + if employee_id: # Check if employee_ids are present in the request + rotating_shifts = RotatingShift.objects.filter(employee_id__in=[employee_id]) + + rotating_shifts = RotatingShift.objects.all() + serializer = self.serializer_class(rotating_shifts, many=True) + return Response(serializer.data, status=200) + + @method_decorator(permission_required("base.add_rotatingshift"), name='dispatch') + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.change_rotatingshift"), name='dispatch') + def put(self, request, pk): + rotating_shift = object_check(RotatingShift, pk) + if rotating_shift is None: + return Response({"error": "RotatingShift not found"}, status=404) + serializer = self.serializer_class(rotating_shift, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.delete_rotatingshift"), name='dispatch') + def delete(self, request, pk): + rotating_shift = object_check(RotatingShift, pk) + if rotating_shift is None: + return Response({"error": "RotatingShift not found"}, status=404) + response, status_code = object_delete(RotatingShift, pk) + return Response(response, status=status_code) + +class IndividualRotatingShiftView(APIView): + serializer_class = RotatingShiftAssignSerializer + permission_classes = [IsAuthenticated] + + def get(self, request, pk=None): + if individual_permssion_check(request) == False: + return Response({'error':'you have no permssion to view'},status=400) + + if pk: + rotating_shift_assign = object_check(RotatingShiftAssign, pk) + if rotating_shift_assign is None: + return Response({"error": "RotatingShiftAssign not found"}, status=404) + serializer = self.serializer_class(rotating_shift_assign) + return Response(serializer.data, status=200) + employee_id = request.GET.get('employee_id',None) + rotating_shift_assigns = RotatingShiftAssign.objects.filter(employee_id= employee_id) + + paginator = PageNumberPagination() + page = paginator.paginate_queryset( + rotating_shift_assigns, request) + serializer = self.serializer_class(page, many=True) + return paginator.get_paginated_response(serializer.data) + + +class RotatingShiftAssignView(APIView): + serializer_class = RotatingShiftAssignSerializer + filterset_class = RotatingShiftAssignFilters + permission_classes = [IsAuthenticated] + + @manager_permission_required("base.view_rotatingshiftassign") + def get(self, request, pk=None): + if pk: + rotating_shift_assign = object_check(RotatingShiftAssign, pk) + if rotating_shift_assign is None: + return Response({"error": "RotatingShiftAssign not found"}, status=404) + serializer = self.serializer_class(rotating_shift_assign) + return Response(serializer.data, status=200) + + rotating_shift_assigns = RotatingShiftAssign.objects.all() + rotating_shift_assigns_filter_queryset = self.filterset_class( + request.GET, queryset=rotating_shift_assigns).qs + field_name = request.GET.get("groupby_field", None) + if field_name: + # groupby workflow + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, rotating_shift_assigns_filter_queryset) + + paginator = PageNumberPagination() + page = paginator.paginate_queryset( + rotating_shift_assigns_filter_queryset, request) + serializer = self.serializer_class(page, many=True) + return paginator.get_paginated_response(serializer.data) + + @manager_permission_required("base.add_rotatingshiftassign") + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @manager_permission_required("base.change_rotatingshiftassign") + def put(self, request, pk): + rotating_shift_assign = object_check(RotatingShiftAssign, pk) + if rotating_shift_assign is None: + return Response({"error": "RotatingShiftAssign not found"}, status=404) + serializer = self.serializer_class( + rotating_shift_assign, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @manager_permission_required("base.delete_rotatingshiftassign") + def delete(self, request, pk): + rotating_shift_assign = object_check(RotatingShiftAssign, pk) + if rotating_shift_assign is None: + return Response({"error": "RotatingShiftAssign not found"}, status=404) + response, status_code = object_delete(RotatingShiftAssign, pk) + return Response(response, status=status_code) + + +class IndividualShiftRequestView(APIView): + serializer_class = ShiftRequestSerializer + permission_classes = [IsAuthenticated] + + def get(self, request, pk=None): + if individual_permssion_check(request) == False: + return Response({'error':'you have no permssion to view'},status=400) + + if pk: + shift_request = object_check(ShiftRequest, pk) + if shift_request is None: + return Response({"error": "EmployeeShift not found"}, status=404) + serializer = self.serializer_class(shift_request) + return Response(serializer.data, status=200) + employee_id = request.GET.get('employee_id',None) + shift_requests = ShiftRequest.objects.filter(employee_id = employee_id) + paginater = PageNumberPagination() + page = paginater.paginate_queryset( + shift_requests, request) + serializer = self.serializer_class(page, many=True) + return paginater.get_paginated_response(serializer.data) + + + +class ShiftRequestView(APIView): + serializer_class = ShiftRequestSerializer + filter_backends = [DjangoFilterBackend] + filterset_class = ShiftRequestFilter + permission_classes = [IsAuthenticated] + + def get_queryset(self, request): + queryset = ShiftRequest.objects.all() + user = request.user + # checking user level permissions + perm = "base.view_shiftrequest" + queryset = permission_based_queryset(user, perm, queryset,user_obj=True) + return queryset + + def get(self, request, pk=None): + # individual section + if pk: + shift_request = object_check(ShiftRequest, pk) + if shift_request is None: + return Response({"error": "ShiftRequest not found"}, status=404) + serializer = self.serializer_class(shift_request) + return Response(serializer.data, status=200) + # filter section + shift_requests = self.get_queryset(request) + shift_requests_filter_queryset = self.filterset_class( + request.GET, queryset=shift_requests).qs + # groupby section + field_name = request.GET.get("groupby_field", None) + if field_name: + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, shift_requests_filter_queryset) + # pagination section + paginator = PageNumberPagination() + page = paginator.paginate_queryset( + shift_requests_filter_queryset, request) + serializer = self.serializer_class(page, many=True) + return paginator.get_paginated_response(serializer.data) + + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @check_approval_status(ShiftRequest, "base.change_shiftrequest") + @manager_or_owner_permission_required(ShiftRequest, "base.change_shiftrequest") + def put(self, request, pk): + shift_request = object_check(ShiftRequest, pk) + if shift_request is None: + return Response({"error": "ShiftRequest not found"}, status=404) + serializer = self.serializer_class(shift_request, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @check_approval_status(ShiftRequest, "base.delete_shiftrequest") + @manager_or_owner_permission_required(ShiftRequest, "base.delete_shiftrequest") + def delete(self, request, pk): + shift_request = object_check(ShiftRequest, pk) + if shift_request is None: + return Response({"error": "ShiftRequest not found"}, status=404) + response, status_code = object_delete(ShiftRequest, pk) + return Response(response, status=status_code) + + +class RotatingWorkTypeView(APIView): + serializer_class = RotatingWorkTypeSerializer + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("base.view_rotatingworktype"), name='dispatch') + def get(self, request, pk=None): + if pk: + rotating_work_type = object_check(RotatingWorkType, pk) + if rotating_work_type is None: + return Response({"error": "RotatingWorkType not found"}, status=404) + serializer = self.serializer_class(rotating_work_type) + return Response(serializer.data, status=200) + + rotating_work_types = RotatingWorkType.objects.all() + serializer = self.serializer_class(rotating_work_types, many=True) + return Response(serializer.data, status=200) + + @method_decorator(permission_required("base.add_rotatingworktype"), name='dispatch') + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.change_rotatingworktype"), name='dispatch') + def put(self, request, pk): + rotating_work_type = object_check(RotatingWorkType, pk) + if rotating_work_type is None: + return Response({"error": "RotatingWorkType not found"}, status=404) + serializer = self.serializer_class( + rotating_work_type, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("base.delete_rotatingworktype"), name='dispatch') + def delete(self, request, pk): + rotating_work_type = object_check(RotatingWorkType, pk) + if rotating_work_type is None: + return Response({"error": "RotatingWorkType not found"}, status=404) + response, status_code = object_delete(RotatingWorkType, pk) + return Response(response, status=status_code) + + +class ShiftRequestApproveView(APIView): + permission_classes = [IsAuthenticated] + + def put(self, request, pk): + shift_request = ShiftRequest.objects.get(id=pk) + if ( + is_reportingmanger(request, shift_request) + or request.user.has_perm("approve_shiftrequest") + or request.user.has_perm("change_shiftrequest") + and not shift_request.approved + ): + """ + here the request will be approved, can send mail right here + """ + if not shift_request.is_any_request_exists(): + shift_request.approved = True + shift_request.canceled = False + shift_request.save() + return Response({"status": "success"}, status=200) + else: + return Response({"error": "Already request exits on same date"}, status=400) + + + return Response({"error": "No permission "}, status=400) + + +class ShiftRequestBulkApproveView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request): + ids = request.data["ids"] + length = len(ids) + count = 0 + for id in ids: + shift_request = ShiftRequest.objects.get(id=id) + if ( + is_reportingmanger(request, shift_request) + or request.user.has_perm("approve_shiftrequest") + or request.user.has_perm("change_shiftrequest") + and not shift_request.approved + ): + """ + here the request will be approved, can send mail right here + """ + shift_request.approved = True + shift_request.canceled = False + employee_work_info = shift_request.employee_id.employee_work_info + employee_work_info.shift_id = shift_request.shift_id + employee_work_info.save() + shift_request.save() + count += 1 + if length == count: + return Response({"status": "success"}, status=200) + return Response({"status": "failed"}, status=400) + + +class ShiftRequestCancelView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + + shift_request = ShiftRequest.objects.get(id=pk) + if ( + is_reportingmanger(request, shift_request) + or request.user.has_perm("base.cancel_shiftrequest") + or shift_request.employee_id == request.user.employee_get + and shift_request.approved == False + ): + shift_request.canceled = True + shift_request.approved = False + shift_request.employee_id.employee_work_info.shift_id = ( + shift_request.previous_shift_id + ) + shift_request.employee_id.employee_work_info.save() + shift_request.save() + return Response({"status": "success"}, status=200) + return Response({"status": "failed"}, status=400) + + +class ShiftRequestBulkCancelView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request): + ids = request.data.get("ids", None) + length = len(ids) + count = 0 + for id in ids: + shift_request = ShiftRequest.objects.get(id=id) + if ( + is_reportingmanger(request, shift_request) + or request.user.has_perm("base.cancel_shiftrequest") + or shift_request.employee_id == request.user.employee_get + and shift_request.approved == False + ): + shift_request.canceled = True + shift_request.approved = False + shift_request.employee_id.employee_work_info.shift_id = ( + shift_request.previous_shift_id + ) + shift_request.employee_id.employee_work_info.save() + shift_request.save() + count += 1 + if length == count: + return Response({"status": "success"}, status=200) + return Response({"status": "failed"}, status=400) + + +class ShiftRequestDeleteView(APIView): + permission_classes = [IsAuthenticated] + + def delete(self, request, pk=None): + + if pk is None: + try: + ids = request.data["ids"] + shift_requests = ShiftRequest.objects.filter(id__in=ids) + shift_requests.delete() + except Exception as e: + return Response({"status": "failed", "error": str(e)}, status=400) + return Response({"status": "success"}, status=200) + try: + shift_request = ShiftRequest.objects.get(id=pk) + if not shift_request.approved: + raise + shift_request.delete() + + except ShiftRequest.DoesNotExist: + return Response({"status": "failed", "error": "Shift request does not exists"}, status=400) + return Response({"status": "deleted"}, status=200) + + +class ShiftRequestExportView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + return shift_request_export(request) + + +class ShiftRequestAllocationView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request,id): + shift_request = ShiftRequest.objects.get(id=id) + if not shift_request.is_any_request_exists(): + shift_request.reallocate_approved = True + shift_request.reallocate_canceled = False + shift_request.save() + return Response({"status": "success"}, status=200) + return Response({"status": "failed"}, status=400) + + +class RotatingShiftAssignExport(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + return rotating_work_type_assign_export(request) + + +class RotatingShiftAssignBulkArchive(APIView): + permission_classes = [IsAuthenticated] + + def put(self, request, status): + ids = request.data.get('ids', None) + try: + rotating_shift_asssign = RotatingShiftAssign.objects.filter( + id__in=ids) + rotating_shift_asssign.update(is_active=status) + return Response({"status": "success"}, status=200) + except Exception as E: + return Response({"error": str(E)}, status=400) + + +class RotatingShiftAssignBulkDelete(APIView): + permission_classes = [IsAuthenticated] + + def delete(self, request): + ids = request.data.get('ids', None) + try: + rotating_shift_asssign = RotatingShiftAssign.objects.filter( + id__in=ids) + rotating_shift_asssign.delete() + return Response({"status": "success"}, status=200) + except Exception as E: + return Response({"error": str(E)}, status=400) + + +class ActiontypeView(APIView): + serializer_class = ActiontypeSerializer + permission_classes = [IsAuthenticated] + + def get(self, request, pk=None): + if pk: + action_type = object_check(Actiontype, pk) + if action_type is None: + return Response({"error": "Actiontype not found"}, status=404) + serializer = self.serializer_class(action_type) + return Response(serializer.data, status=200) + action_types = Actiontype.objects.all() + paginater = PageNumberPagination() + page = paginater.paginate_queryset(action_types, request) + serializer = self.serializer_class(page, many=True) + return paginater.get_paginated_response(serializer.data) + + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + def put(self, request, pk): + action_type = object_check(Actiontype, pk) + if action_type is None: + return Response({"error": "Actiontype not found"}, status=404) + serializer = self.serializer_class( + action_type, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + def delete(self, request, pk): + action_type = object_check(Actiontype, pk) + if action_type is None: + return Response({"error": "Actiontype not found"}, status=404) + response, status_code = object_delete(Actiontype, pk) + return Response(response, status=status_code) + +class RotatingWorKTypePermissionCheck(APIView): + permission_classes = [IsAuthenticated] + def get(self,request,id): + manager = Employee.objects.filter(id=id).first().get_reporting_manager() + if request.user.has_perm('base.add_rotatingworktypeassign') or request.user.employee_get == manager : + return Response(status=200) + return Response(status=400) + +class RotatingShiftPermissionCheck(APIView): + permission_classes = [IsAuthenticated] + def get(self,request,id): + manager = Employee.objects.filter(id=id).first().get_reporting_manager() + if request.user.has_perm('base.add_rotatingshiftassign') or request.user.employee_get == manager : + return Response(status=200) + return Response(status=400) + +class WorktypeRequestApprovePermissionCheck(APIView): + permission_classes = [IsAuthenticated] + def get(self,request): + instance = request.user.employee_get + if ( + _is_reportingmanger(request, instance) + or request.user.has_perm("approve_shiftrequest") + or request.user.has_perm("change_shiftrequest") + ): + return Response(status=200) + return Response(status=400) + +class ShiftRequestApprovePermissionCheck(APIView): + permission_classes = [IsAuthenticated] + def get(self,request): + instance = request.user.employee_get + if ( + _is_reportingmanger(request, instance) + or request.user.has_perm("approve_shiftrequest") + or request.user.has_perm("change_shiftrequest") + ): + return Response(status=200) + return Response(status=400) + +class EmployeeTabPermissionCheck(APIView): + permission_classes = [IsAuthenticated] + + def get(self,request): + instance = request.user.employee_get + if ( + _is_reportingmanger(request, instance) + or request.user.has_perms(["attendance.view_worktyperequest","perms.attendance.view_shiftrequest"]) + + ): + return Response(status=200) + return Response(status=400) diff --git a/horilla_api/api_views/employee/views.py b/horilla_api/api_views/employee/views.py new file mode 100644 index 000000000..9b064ac05 --- /dev/null +++ b/horilla_api/api_views/employee/views.py @@ -0,0 +1,749 @@ +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework.pagination import PageNumberPagination +from django.http import Http404 +from ...api_decorators.base.decorators import manager_or_owner_permission_required, manager_permission_required +from employee.filters import DisciplinaryActionFilter, DocumentRequestFilter, EmployeeFilter +from employee.models import DisciplinaryAction, Employee, EmployeeBankDetails, EmployeeWorkInformation, Policy,EmployeeType +from employee.views import work_info_export, work_info_import +from ...api_methods.base.methods import groupby_queryset, permission_based_queryset +from ...api_decorators.employee.decorators import or_condition +from horilla.decorators import owner_can_enter +from horilla_documents.models import Document, DocumentRequest +from ... api_serializers.employee.serializers import (DisciplinaryActionSerializer, DocumentRequestSerializer, DocumentSerializer, + EmployeeBankDetailsSerializer, EmployeeListSerializer, EmployeeSelectorSerializer, EmployeeSerializer, EmployeeTypeSerializer, EmployeeWorkInformationSerializer, PolicySerializer) +from django_filters.rest_framework import DjangoFilterBackend +from django.contrib.auth.decorators import permission_required +from django.utils.decorators import method_decorator +from rest_framework.permissions import IsAuthenticated +from django.db.models import ProtectedError +from notifications.signals import notify +from django.db.models import Q + + +class EmployeeTypeAPIView(APIView): + """ + Retrieves employee types. + + Methods: + get(request, pk=None): Returns a single employee type if pk is provided, otherwise returns all employee types. + """ + def get(self,request,pk=None): + if pk: + employee_type = EmployeeType.objects.get(id=pk) + serializer = EmployeeTypeSerializer(employee_type) + return Response(serializer.data,status=200) + employee_type = EmployeeType.objects.all() + serializer = EmployeeTypeSerializer(employee_type,many=True) + return Response(serializer.data,status=200) + +class EmployeeAPIView(APIView): + """ + Handles CRUD operations for employees. + + Methods: + get(request, pk=None): + - Retrieves a single employee by pk if provided. + - Retrieves and filters all employees if pk is not provided. + + post(request): + - Creates a new employee if the user has the 'employee.change_employee' permission. + + put(request, pk): + - Updates an existing employee if the user is the employee, a manager, or has 'employee.change_employee' permission. + + delete(request, pk): + - Deletes an employee if the user has the 'employee.delete_employee' permission. + """ + + filter_backends = [DjangoFilterBackend] + filterset_class = EmployeeFilter + permission_classes = [IsAuthenticated] + + def get(self, request, pk=None): + + if pk: + try: + employee = Employee.objects.get(pk=pk) + except Employee.DoesNotExist: + return Response({"error": "Employee does not exist"}, status=status.HTTP_404_NOT_FOUND) + + serializer = EmployeeSerializer(employee) + return Response(serializer.data) + + paginator = PageNumberPagination() + employees_queryset = Employee.objects.all() + employees_filter_queryset = self.filterset_class( + request.GET, queryset=employees_queryset).qs + + field_name = request.GET.get("groupby_field", None) + if field_name: + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, employees_filter_queryset) + + page = paginator.paginate_queryset(employees_filter_queryset, request) + serializer = EmployeeSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + @manager_permission_required("employee.change_employee") + def post(self, request): + serializer = EmployeeSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def put(self, request, pk): + user = request.user + employee = Employee.objects.get(pk=pk) + is_manager = EmployeeWorkInformation.objects.filter( + reporting_manager_id=user.employee_get).first() + if employee == user.employee_get or is_manager or user.has_perm("employee.change_employee"): + serializer = EmployeeSerializer( + employee, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response({"error": "You don't have permission"}, status=400) + + @method_decorator(permission_required('employee.delete_employee')) + def delete(self, request, pk): + try: + employee = Employee.objects.get(pk=pk) + employee.delete() + except Employee.DoesNotExist: + return Response({"error": "Employee does not exist"}, status=status.HTTP_404_NOT_FOUND) + except ProtectedError as e: + return Response({"error": str(e)}, status=status.HTTP_204_NO_CONTENT) + return Response(status=status.HTTP_204_NO_CONTENT) + + +class EmployeeListAPIView(APIView): + """ + Retrieves a paginated list of employees with optional search functionality. + + Methods: + get(request): + - Returns a paginated list of employees. + - Optionally filters employees based on a search query in the first or last name. + """ + + permission_classes = [IsAuthenticated] + + def get(self, request): + paginator = PageNumberPagination() + paginator.page_size = 13 + search = request.query_params.get('search',None) + if search: + employees_queryset = Employee.objects.filter(Q(employee_first_name__icontains = search)|Q(employee_last_name__icontains = search)) + else: + employees_queryset = Employee.objects.all() + page = paginator.paginate_queryset(employees_queryset, request) + serializer = EmployeeListSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + +class EmployeeBankDetailsAPIView(APIView): + """ + Manage employee bank details with CRUD operations. + + Methods: + get(request, pk=None): + - Retrieves bank details for a specific employee if `pk` is provided. + - Returns a paginated list of all employee bank details if `pk` is not provided. + + post(request): + - Creates a new bank detail entry for an employee. + + put(request, pk): + - Updates existing bank details for an employee identified by `pk`. + + delete(request, pk): + - Deletes bank details for an employee identified by `pk`. + """ + + permission_classes = [IsAuthenticated] + + def get_queryset(self): + queryset = EmployeeBankDetails.objects.all() + user = self.request.user + # checking user level permissions + perm = "base.view_employeebankdetails" + queryset = permission_based_queryset(user, perm, queryset) + return queryset + + def get(self, request, pk=None): + if pk: + try: + bank_detail = EmployeeBankDetails.objects.get(pk=pk) + except EmployeeBankDetails.DoesNotExist: + return Response({"error": "Bank details do not exist"}, status=status.HTTP_404_NOT_FOUND) + + serializer = EmployeeBankDetailsSerializer(bank_detail) + return Response(serializer.data) + paginator = PageNumberPagination() + employee_bank_details = self.get_queryset(request) + page = paginator.paginate_queryset(employee_bank_details, request) + serializer = EmployeeBankDetailsSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + @manager_or_owner_permission_required(EmployeeBankDetails, "employee.add_employeebankdetails") + def post(self, request): + serializer = EmployeeBankDetailsSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @manager_or_owner_permission_required(EmployeeBankDetails, "employee.add_employeebankdetails") + def put(self, request, pk): + try: + bank_detail = EmployeeBankDetails.objects.get(pk=pk) + except EmployeeBankDetails.DoesNotExist: + return Response({"error": "Bank details do not exist"}, status=status.HTTP_404_NOT_FOUND) + + serializer = EmployeeBankDetailsSerializer( + bank_detail, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @manager_permission_required("employee.change_employeebankdetails") + def delete(self, request, pk): + try: + bank_detail = EmployeeBankDetails.objects.get(pk=pk) + bank_detail.delete() + except EmployeeBankDetails.DoesNotExist: + return Response({"error": "Bank details do not exist"}, status=status.HTTP_404_NOT_FOUND) + except Exception as E: + return Response({"error": str(E)}, status=400) + + return Response(status=status.HTTP_204_NO_CONTENT) + + + +class EmployeeWorkInformationAPIView(APIView): + """ + Manage employee work information with CRUD operations. + + Methods: + get(request, pk): + - Retrieves work information for a specific employee identified by `pk`. + + post(request): + - Creates a new work information entry for an employee. + + put(request, pk): + - Updates existing work information for an employee identified by `pk`. + + delete(request, pk): + - Deletes work information for an employee identified by `pk`. + """ + permission_classes = [IsAuthenticated] + + def get(self, request, pk): + work_info = EmployeeWorkInformation.objects.get(pk=pk) + serializer = EmployeeWorkInformationSerializer(work_info) + return Response(serializer.data) + + @manager_permission_required("employee.add_employeeworkinformation") + def post(self, request): + serializer = EmployeeWorkInformationSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @manager_permission_required("employee.change_employeeworkinformation") + def put(self, request, pk): + try: + work_info = EmployeeWorkInformation.objects.get(pk=pk) + except EmployeeWorkInformation.DoesNotExist: + raise Http404 + serializer = EmployeeWorkInformationSerializer( + work_info, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + + + @method_decorator(permission_required("employee.delete_employeeworkinformation"), name='dispatch') + def delete(self, request, pk): + try: + work_info = EmployeeWorkInformation.objects.get(pk=pk) + except EmployeeWorkInformation.DoesNotExist: + raise Http404 + work_info.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + + +class EmployeeWorkInfoExportView(APIView): + """ + Endpoint for exporting employee work information. + + Methods: + get(request): + - Exports work information data based on user permissions. + """ + permission_classes = [IsAuthenticated] + + @manager_permission_required("employee.add_employeeworkinformation") + def get(self, request): + return work_info_export(request) + +class EmployeeWorkInfoImportView(APIView): + """ + Endpoint for importing employee work information. + + Methods: + get(request): + - Handles the importing of work information data based on user permissions. + """ + + permission_classes = [IsAuthenticated] + + @manager_permission_required("employee.add_employeeworkinformation") + def get(self, request): + return work_info_import(request) + + +class EmployeeBulkUpdateView(APIView): + """ + Endpoint for bulk updating employee and work information. + + Permissions: + - Requires authentication and "change_employee" permission. +0 + Methods: + put(request): + - Updates multiple employees and their work information. + """ + + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("employee.change_employee"), name='dispatch') + def put(self, request): + employee_ids = request.data.get('ids', []) + employees = Employee.objects.filter(id__in=employee_ids) + employee_work_info = EmployeeWorkInformation.objects.filter( + employee_id__in=employees) + employee_data = request.data.get('employee_data', {}) + work_info_data = request.data.get("employee_work_info", {}) + fields_to_remove = [ + "badge_id", + "employee_first_name", + "employee_last_name", + "is_active", + "email", + "phone", + "employee_bank_details__account_number", + ] + for field in fields_to_remove: + employee_data.pop(field, None) + work_info_data.pop(field, None) + + try: + employees.update(**employee_data) + employee_work_info.update(**work_info_data) + except Exception as e: + return Response({"error": str(e)}, status=400) + return Response({"status": "success"}, status=200) + + +class DisciplinaryActionAPIView(APIView): + """ + Endpoint for managing disciplinary actions. + + Permissions: + - Requires authentication. + + Methods: + get(request, pk=None): + - Retrieves a specific disciplinary action by `pk` or lists all disciplinary actions with optional filtering. + + post(request): + - Creates a new disciplinary action. + + put(request, pk): + - Updates an existing disciplinary action by `pk`. + + delete(request, pk): + - Deletes a specific disciplinary action by `pk`. + """ + filterset_class = DisciplinaryActionFilter + permission_classes = [IsAuthenticated] + + def get_object(self, pk): + try: + return DisciplinaryAction.objects.get(pk=pk) + except DisciplinaryAction.DoesNotExist: + raise Http404 + + def get(self, request, pk=None): + if pk: + disciplinary_action = self.get_object(pk) + serializer = DisciplinaryActionSerializer(disciplinary_action) + return Response(serializer.data, status=200) + else: + paginator = PageNumberPagination() + disciplinary_actions = DisciplinaryAction.objects.all() + disciplinary_action_filter_queryset = self.filterset_class( + request.GET, queryset=disciplinary_actions).qs + page = paginator.paginate_queryset( + disciplinary_action_filter_queryset, request) + serializer = DisciplinaryActionSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + def post(self, request): + serializer = DisciplinaryActionSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def put(self, request, pk): + disciplinary_action = self.get_object(pk) + serializer = DisciplinaryActionSerializer( + disciplinary_action, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, pk): + disciplinary_action = self.get_object(pk) + disciplinary_action.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class PolicyAPIView(APIView): + """ + Endpoint for managing policies. + + Permissions: + - Requires authentication. + + Methods: + get(request, pk=None): + - Retrieves a specific policy by `pk` or lists all policies with optional search functionality. + + post(request): + - Creates a new policy. + + put(request, pk): + - Updates an existing policy by `pk`. + + delete(request, pk): + - Deletes a specific policy by `pk`. + """ + permission_classes = [IsAuthenticated] + + def get_object(self, pk): + try: + return Policy.objects.get(pk=pk) + except Policy.DoesNotExist: + raise Http404 + + def get(self, request, pk=None): + if pk: + policy = self.get_object(pk) + serializer = PolicySerializer(policy) + return Response(serializer.data) + else: + search = request.GET.get("search", None) + if search: + policies = Policy.objects.filter(title__icontains=search) + else: + policies = Policy.objects.all() + serializer = PolicySerializer(policies, many=True) + paginator = PageNumberPagination() + page = paginator.paginate_queryset( + policies, request) + serializer = PolicySerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + def post(self, request): + serializer = PolicySerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + def put(self, request, pk): + policy = self.get_object(pk) + serializer = PolicySerializer(policy, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=400) + + def delete(self, request, pk): + policy = self.get_object(pk) + policy.delete() + return Response(status=204) + + +class DocumentRequestAPIView(APIView): + """ + Endpoint for managing document requests. + + Permissions: + - Requires authentication. + - Specific actions require manager-level permissions. + + Methods: + get(request, pk=None): + - Retrieves a specific document request by `pk` or lists all document requests with pagination. + + post(request): + - Creates a new document request and notifies relevant employees. + + put(request, pk): + - Updates an existing document request by `pk`. + + delete(request, pk): + - Deletes a specific document request by `pk`. + """ + permission_classes = [IsAuthenticated] + + def get_object(self, pk): + try: + return DocumentRequest.objects.get(pk=pk) + except DocumentRequest.DoesNotExist: + raise Http404 + + def get(self, request, pk=None): + if pk: + document_request = self.get_object(pk) + serializer = DocumentRequestSerializer(document_request) + return Response(serializer.data) + else: + document_requests = DocumentRequest.objects.all() + pagination = PageNumberPagination() + page = pagination.paginate_queryset( + document_requests, request) + serializer = DocumentRequestSerializer( + page, many=True) + return pagination.get_paginated_response(serializer.data) + + @manager_permission_required("horilla_documents.add_documentrequests") + def post(self, request): + serializer = DocumentRequestSerializer(data=request.data) + if serializer.is_valid(): + obj = serializer.save() + try: + employees = [ + user.employee_user_id for user in obj.employee_id.all()] + + notify.send( + request.user.employee_get, + recipient=employees, + verb=f"{request.user.employee_get} requested a document.", + verb_ar=f"طلب {request.user.employee_get} مستنداً.", + verb_de=f"{request.user.employee_get} hat ein Dokument angefordert.", + verb_es=f"{request.user.employee_get} solicitó un documento.", + verb_fr=f"{request.user.employee_get} a demandé un document.", + redirect="/employee/employee-profile", + icon="chatbox-ellipses", + api_redirect=f"/api/employee/document-request/{obj.id}" + ) + except: + pass + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @manager_permission_required("horilla_documents.change_documentrequests") + def put(self, request, pk): + document_request = self.get_object(pk) + serializer = DocumentRequestSerializer( + document_request, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @method_decorator(permission_required("employee.delete_employee", raise_exception=True)) + def delete(self, request, pk): + document_request = self.get_object(pk) + document_request.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class DocumentAPIView(APIView): + filterset_class = DocumentRequestFilter + permission_classes = [IsAuthenticated] + + def get_object(self, pk): + try: + return Document.objects.get(pk=pk) + except Document.DoesNotExist: + raise Http404 + + def get(self, request, pk=None): + if pk: + document = self.get_object(pk) + serializer = DocumentSerializer(document) + return Response(serializer.data) + else: + documents = Document.objects.all() + document_requests_filtered = self.filterset_class( + request.GET, queryset=documents).qs + paginator = PageNumberPagination() + page = paginator.paginate_queryset( + document_requests_filtered, request) + serializer = DocumentSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + @manager_or_owner_permission_required(DocumentRequest, "horilla_documents.add_document") + def post(self, request): + serializer = DocumentSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + try: + notify.send( + request.user.employee_get, + recipient=request.user.employee_get.get_reporting_manager().employee_user_id, + verb=f"{request.user.employee_get} uploaded a document", + verb_ar=f"قام {request.user.employee_get} بتحميل مستند", + verb_de=f"{request.user.employee_get} hat ein Dokument hochgeladen", + verb_es=f"{request.user.employee_get} subió un documento", + verb_fr=f"{request.user.employee_get} a téléchargé un document", + redirect=f"/employee/employee-view/{request.user.employee_get.id}/", + icon="chatbox-ellipses", + api_redirect=f"/api/employee/documents/" + ) + except: + pass + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @method_decorator(owner_can_enter("horilla_documents.change_document", Employee)) + def put(self, request, pk): + document = self.get_object(pk) + serializer = DocumentSerializer(document, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @method_decorator(owner_can_enter("horilla_documents.delete_document", Employee)) + def delete(self, request, pk): + document = self.get_object(pk) + document.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class DocumentRequestApproveRejectView(APIView): + permission_classes = [IsAuthenticated] + + @manager_permission_required("horilla_documents.add_document") + def post(self, request, id, status): + document = Document.objects.filter(id=id).first() + document.status = status + document.save() + return Response({"status": "success"}, status=200) + + +class DocumentBulkApproveRejectAPIView(APIView): + permission_classes = [IsAuthenticated] + + @manager_permission_required("horilla_documents.add_document") + def put(self, request): + ids = request.data.get("ids", None) + status = request.data.get("status", None) + status_code = 200 + + if ids: + documents = Document.objects.filter(id__in=ids) + response = [] + for document in documents: + if not document.document: + status_code = 400 + response.append( + {"id": document.id, "error": "No documents"}) + continue + response.append({"id": document.id, "status": "success"}) + document.status = status + document.save() + return Response(response, status=status_code) + + +class EmployeeBulkArchiveView(APIView): + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("employee.delete_employee", raise_exception=True)) + def post(self, request, is_active): + ids = request.data.get("ids") + error = [] + for employee_id in ids: + employee = Employee.objects.get(id=employee_id) + employee.is_active = is_active + employee.employee_user_id.is_active = is_active + if employee.get_archive_condition() is False: + employee.save() + error.append({"employee": str(employee), + "error": "Related model found for this employee. "}) + return Response(error, status=200) + + +class EmployeeArchiveView(APIView): + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("employee.delete_employee", raise_exception=True)) + def post(self, request, id, is_active): + employee = Employee.objects.get(id=id) + employee.is_active = is_active + employee.employee_user_id.is_active = is_active + response = None + if employee.get_archive_condition() is False: + employee.save() + else: + response = {"employee": str( + employee), "error": employee.get_archive_condition()} + return Response(response, status=200) + + +class EmployeeSelectorView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + employee = request.user.employee_get + employees = Employee.objects.filter(employee_user_id=request.user) + + + is_manager = EmployeeWorkInformation.objects.filter( + reporting_manager_id=employee + ).exists() + + if is_manager: + employees = Employee.objects.filter( + Q(pk=employee.pk) | Q( + employee_work_info__reporting_manager_id=employee) + ) + if request.user.has_perm("employee.view_employee"): + employees = Employee.objects.all() + + paginator = PageNumberPagination() + page = paginator.paginate_queryset( + employees, request) + serializer = EmployeeSelectorSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + + +class ReportingManagerCheck(APIView): + permission_classes = [IsAuthenticated] + + def get(self,request): + if Employee.objects.filter(employee_work_info__reporting_manager_id =request.user.employee_get): + return Response(status=200) + return Response(status=404) + + + \ No newline at end of file diff --git a/horilla_api/api_views/leave/views.py b/horilla_api/api_views/leave/views.py new file mode 100644 index 000000000..3b72cca36 --- /dev/null +++ b/horilla_api/api_views/leave/views.py @@ -0,0 +1,1102 @@ +from rest_framework.views import APIView +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from horilla_api.api_serializers.leave.serializers import * +from leave.models import LeaveRequest +from rest_framework.pagination import PageNumberPagination +from django.http import Http404 +from django.contrib.auth.models import AnonymousUser +from django.http import QueryDict +from django_filters.rest_framework import DjangoFilterBackend +from leave.filters import * +from django.contrib.auth.decorators import permission_required +from django.utils.decorators import method_decorator +from django.db.models import Count +from ...api_decorators.base.decorators import manager_permission_required +from ...api_methods.base.methods import groupby_queryset +from leave.methods import filter_conditional_leave_request +from base.methods import filtersubordinates +from notifications.signals import notify +import contextlib + + +class EmployeeAvailableLeaveGetAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + employee = request.user.employee_get + available_leave = employee.available_leave.all() + paginator = PageNumberPagination() + page = paginator.paginate_queryset(available_leave, request) + serializer = GetAvailableLeaveTypeSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + +class EmployeeLeaveRequestGetCreateAPIView(APIView): + permission_classes = [IsAuthenticated] + filter_backends = [DjangoFilterBackend] + filterset_class = UserLeaveRequestFilter + + def get(self, request): + employee = request.user.employee_get + leave_request = employee.leaverequest_set.all().order_by("-id") + filterset = self.filterset_class(request.GET, queryset=leave_request) + paginator = PageNumberPagination() + field_name = request.GET.get("groupby_field", None) + if field_name: + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, filterset.qs) + page = paginator.paginate_queryset(filterset.qs, request) + serializer = userLeaveRequestGetAllSerilaizer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + def post(self, request): + employee_id = request.user.employee_get.id + data = request.data + if isinstance(data, QueryDict): + data = data.dict() + data["employee_id"] = employee_id + data["end_date"] = ( + data.get("start_date") if not data.get("end_date") else data.get("end_date") + ) + serializer = LeaveRequestCreateUpdateSerializer(data=data) + if serializer.is_valid(): + leave_request = serializer.save() + with contextlib.suppress(Exception): + notify.send( + request.user.employee_get, + recipient=leave_request.employee_id.employee_work_info.reporting_manager_id.employee_user_id, + verb="You have a new leave request to validate.", + verb_ar="لديك طلب إجازة جديد يجب التحقق منه.", + verb_de="Sie haben eine neue Urlaubsanfrage zur Validierung.", + verb_es="Tiene una nueva solicitud de permiso que debe validar.", + verb_fr="Vous avez une nouvelle demande de congé à valider.", + icon="people-circle", + redirect=f"/leave/request-view?id={leave_request.id}", + api_redirect=f"/api/leave/request/{leave_request.id}/", + ) + return Response( + userLeaveRequestGetAllSerilaizer(leave_request).data, status=201 + ) + return Response(serializer.errors, status=400) + + +class EmployeeLeaveRequestUpdateDeleteAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_leave_request(self, request, pk): + try: + return LeaveRequest.objects.get( + pk=pk, employee_id=request.user.employee_get + ) + except LeaveRequest.DoesNotExist as e: + raise serializers.ValidationError(e) + + def get(self, request, pk): + leave_request = self.get_leave_request(request, pk) + serializer = UserLeaveRequestGetSerilaizer(leave_request) + return Response(serializer.data, status=200) + + def put(self, request, pk): + leave_request = self.get_leave_request(request, pk) + employee_id = request.user.employee_get + if ( + leave_request.status == "requested" + and leave_request.employee_id == employee_id + ): + data = request.data + if isinstance(data, QueryDict): + data = data.dict() + data["employee_id"] = employee_id.id + data["end_date"] = ( + data.get("start_date") + if not data.get("end_date") + else data.get("end_date") + ) + serializer = LeaveRequestCreateUpdateSerializer(leave_request, data=data) + if serializer.is_valid(): + leave_request = serializer.save() + return Response( + UserLeaveRequestGetSerilaizer(leave_request).data, status=201 + ) + return Response(serializer.errors, status=400) + raise serializers.ValidationError({"error": "Access Denied.."}) + + def delete(self, request, pk): + leave_request = self.get_leave_request(request, pk) + employee_id = request.user.employee_get + if ( + leave_request.status == "requested" + and leave_request.employee_id == employee_id + ): + leave_request.delete() + return Response( + {"message": "Leave request deleted successfully.."}, status=200 + ) + raise serializers.ValidationError({"error": "Access Denied.."}) + + +class LeaveTypeGetCreateAPIView(APIView): + permission_classes = [IsAuthenticated] + filter_backends = [DjangoFilterBackend] + filterset_class = LeaveTypeFilter + + # @method_decorator(permission_required('leave.view_leavetype', raise_exception=True), name='dispatch') + def get(self, request): + leave_type = LeaveType.objects.all() + filterset = self.filterset_class(request.GET, queryset=leave_type) + paginator = PageNumberPagination() + page = paginator.paginate_queryset(filterset.qs, request) + serializer = LeaveTypeAllGetSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + @method_decorator( + permission_required("leave.add_leavetype", raise_exception=True), + name="dispatch", + ) + def post(self, request): + serializer = LeaveTypeGetCreateSerilaizer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + +class LeaveTypeGetUpdateDeleteAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_leave_type(self, pk): + try: + return LeaveType.objects.get(pk=pk) + except LeaveType.DoesNotExist as e: + raise serializers.ValidationError(e) + + @method_decorator( + permission_required("leave.view_leavetype", raise_exception=True), + name="dispatch", + ) + def get(self, request, pk): + leave_type = self.get_leave_type(pk) + serializer = LeaveTypeGetCreateSerilaizer(leave_type) + return Response(serializer.data, status=200) + + @method_decorator( + permission_required("leave.change_leavetype", raise_exception=True), + name="dispatch", + ) + def put(self, request, pk): + leave_type = self.get_leave_type(pk) + serializer = LeaveTypeGetCreateSerilaizer(leave_type, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @method_decorator( + permission_required("leave.delete_leavetype", raise_exception=True), + name="dispatch", + ) + def delete(self, request, pk): + leave_type = self.get_leave_type(pk) + leave_type.delete() + return Response(status=201) + + +class LeaveAllocationRequestGetCreateAPIView(APIView): + permission_classes = [IsAuthenticated] + filter_backends = [DjangoFilterBackend] + filterset_class = LeaveAllocationRequestFilter + + def get_user(self, request): + user = request.user + if isinstance(user, AnonymousUser): + raise Http404("AnonymousUser") + return user + + @manager_permission_required("leave.view_leaveallocationrequest") + def get(self, request): + allocation_requests = LeaveAllocationRequest.objects.all().order_by("-id") + queryset = filtersubordinates( + request, allocation_requests, "leave.view_leaveallocationrequest" + ) + filterset = self.filterset_class(request.GET, queryset=queryset) + paginator = PageNumberPagination() + field_name = request.GET.get("groupby_field", None) + if field_name: + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, filterset.qs) + page = paginator.paginate_queryset(filterset.qs, request) + serializer = LeaveAllocationRequestGetSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + def post(self, request): + data = request.data + employee_id = self.get_user(request).employee_get.id + if isinstance(data, QueryDict): + data = data.dict() + data["created_by"] = employee_id + serializer = LeaveAllocationRequestCreateSerializer(data=data) + if serializer.is_valid(): + allocation_request = serializer.save() + with contextlib.suppress(Exception): + notify.send( + request.user.employee_get, + recipient=allocation_request.employee_id.employee_work_info.reporting_manager_id.employee_user_id, + verb=f"New leave allocation request created for {allocation_request.employee_id}.", + verb_ar=f"تم إنشاء طلب تخصيص إجازة جديد لـ {allocation_request.employee_id}.", + verb_de=f"Neue Anfrage zur Urlaubszuweisung erstellt für {allocation_request.employee_id}.", + verb_es=f"Nueva solicitud de asignación de permisos creada para {allocation_request.employee_id}.", + verb_fr=f"Nouvelle demande d'allocation de congé créée pour {allocation_request.employee_id}.", + icon="people-cicle", + redirect=f"/leave/leave-allocation-request-view?id={allocation_request.id}", + api_redirect=f"/api/leave/allocation-request/{allocation_request.id}/", + ) + return Response( + LeaveAllocationRequestGetSerializer(allocation_request).data, status=201 + ) + return Response(serializer.errors, status=400) + + +class LeaveAllocationRequestGetUpdateDeleteAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_leave_allocation_request(self, pk): + try: + return LeaveAllocationRequest.objects.get(pk=pk) + except LeaveAllocationRequest.DoesNotExist as e: + raise serializers.ValidationError(e) + + @manager_permission_required("leave.view_leaveallocationrequest") + def get(self, request, pk): + allocation_request = self.get_leave_allocation_request(pk) + serializer = LeaveAllocationRequestGetSerializer(allocation_request) + return Response(serializer.data, status=200) + + @manager_permission_required("leave.change_leaveallocationrequest") + def put(self, request, pk): + allocation_request = self.get_leave_allocation_request(pk) + if allocation_request.status == "requested": + serializer = LeaveAllocationRequestSerilaizer( + allocation_request, data=request.data + ) + if serializer.is_valid(): + allocation_request = serializer.save() + return Response( + LeaveAllocationRequestGetSerializer(allocation_request).data, + status=201, + ) + return Response(serializer.errors, status=400) + raise serializers.ValidationError({"error": "Access Denied.."}) + + @manager_permission_required("leave.delete_leaveallocationrequest") + def delete(self, request, pk): + allocation_request = self.get_leave_allocation_request(pk) + if allocation_request.status == "requested": + allocation_request.delete() + return Response(status=200) + raise serializers.ValidationError({"error": "Access Denied.."}) + + +class AssignLeaveGetCreateAPIView(APIView): + + permission_classes = [IsAuthenticated] + filter_backends = [DjangoFilterBackend] + filterset_class = AssignedLeaveFilter + + @method_decorator( + permission_required("leave.view_availableleave", raise_exception=True), + name="dispatch", + ) + def get(self, request): + available_leave = AvailableLeave.objects.all().order_by("-id") + queryset = filtersubordinates( + request, available_leave, "leave.view_availableleave" + ) + filterset = self.filterset_class(request.GET, queryset=queryset) + paginator = PageNumberPagination() + field_name = request.GET.get("groupby_field", None) + if field_name: + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, filterset.qs) + page = paginator.paginate_queryset(filterset.qs, request) + serializer = AssignLeaveGetSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + @method_decorator( + permission_required("leave.add_availableleave", raise_exception=True), + name="dispatch", + ) + def post(self, request): + serializer = AssignLeaveCreateSerializer(data=request.data) + if serializer.is_valid(): + employee_ids = serializer.validated_data.get("employee_ids") + leave_type_ids = serializer.validated_data.get("leave_type_ids") + for employee_id in employee_ids: + for leave_type_id in leave_type_ids: + if not AvailableLeave.objects.filter( + employee_id=employee_id, leave_type_id=leave_type_id + ).exists(): + AvailableLeave.objects.create( + employee_id=employee_id, + leave_type_id=leave_type_id, + available_days=leave_type_id.total_days, + ) + with contextlib.suppress(Exception): + notify.send( + request.user.employee_get, + recipient=employee_id.employee_user_id, + verb="New leave type is assigned to you", + verb_ar="تم تعيين نوع إجازة جديد لك", + verb_de="Dir wurde ein neuer Urlaubstyp zugewiesen", + verb_es="Se te ha asignado un nuevo tipo de permiso", + verb_fr="Un nouveau type de congé vous a été attribué", + icon="people-circle", + redirect="/leave/user-request-view", + api_redirect="/api/leave/user-request/", + ) + return Response(status=201) + return Response(serializer.errors, status=400) + + +class AssignLeaveGetUpdateDeleteAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_available_leave(self, pk): + try: + return AvailableLeave.objects.get(pk=pk) + except AvailableLeave.DoesNotExist as e: + raise serializers.ValidationError(e) + + @method_decorator( + permission_required("leave.view_availableleave", raise_exception=True), + name="dispatch", + ) + def get(self, request, pk): + available_leave = self.get_available_leave(pk) + serializer = AssignLeaveGetSerializer(available_leave) + return Response(serializer.data, status=200) + + @method_decorator( + permission_required("leave.change_availableleave", raise_exception=True), + name="dispatch", + ) + def put(self, request, pk): + available_leave = self.get_available_leave(pk) + serializer = AvailableLeaveUpdateSerializer(available_leave, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(status=201) + return Response(serializer.errors, status=400) + + @method_decorator( + permission_required("leave.delete_availableleave", raise_exception=True), + name="dispatch", + ) + def delete(self, request, pk): + available_leave = self.get_available_leave(pk) + available_leave.delete() + return Response(status=200) + + +class LeaveRequestGetCreateAPIView(APIView): + permission_classes = [IsAuthenticated] + filter_backends = [DjangoFilterBackend] + filterset_class = LeaveRequestFilter + + @manager_permission_required("leave.view_leaverequest") + def get(self, request): + leave_request = LeaveRequest.objects.all().order_by("-id") + multiple_approvals = filter_conditional_leave_request(request) + queryset = ( + filtersubordinates(request, leave_request, "leave.view_leaverequest") + | multiple_approvals + ) + filterset = self.filterset_class(request.GET, queryset=queryset) + paginator = PageNumberPagination() + field_name = request.GET.get("groupby_field", None) + if field_name: + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, filterset.qs) + page = paginator.paginate_queryset(filterset.qs, request) + serializer = LeaveRequestGetAllSerilaizer( + page, context={"request": request}, many=True + ) + return paginator.get_paginated_response(serializer.data) + + @manager_permission_required("leave.add_leaverequest") + def post(self, request): + data = request.data + if isinstance(data, QueryDict): + data = data.dict() + data["end_date"] = ( + data.get("start_date") if not data.get("end_date") else data.get("end_date") + ) + serializer = LeaveRequestCreateUpdateSerializer(data=data) + if serializer.is_valid(): + leave_request = serializer.save() + with contextlib.suppress(Exception): + notify.send( + request.user.employee_get, + recipient=leave_request.employee_id.employee_work_info.reporting_manager_id.employee_user_id, + verb=f"New leave request created for {leave_request.employee_id}.", + verb_ar=f"تم إنشاء طلب إجازة جديد لـ {leave_request.employee_id}.", + verb_de=f"Neuer Urlaubsantrag erstellt für {leave_request.employee_id}.", + verb_es=f"Nueva solicitud de permiso creada para {leave_request.employee_id}.", + verb_fr=f"Nouvelle demande de congé créée pour {leave_request.employee_id}.", + icon="people-circle", + redirect=f"/leave/request-view?id={leave_request.id}", + api_redirect=f"/api/leave/request/{leave_request.id}/", + ) + return Response( + LeaveRequestGetSerilaizer( + leave_request, context={"request": request} + ).data, + status=201, + ) + return Response(serializer.errors, status=400) + + +class LeaveRequestGetUpdateDeleteAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_leave_request(self, pk): + try: + return LeaveRequest.objects.get(pk=pk) + except LeaveRequest.DoesNotExist as e: + raise serializers.ValidationError(e) + + @manager_permission_required("leave.view_leaverequest") + def get(self, request, pk): + leave_request = self.get_leave_request(pk) + serializer = LeaveRequestGetSerilaizer( + leave_request, context={"request": request} + ) + return Response(serializer.data, status=200) + + @manager_permission_required("leave.change_leaverequest") + def put(self, request, pk): + leave_request = self.get_leave_request(pk) + if leave_request.status == "requested": + data = request.data + if isinstance(data, QueryDict): + data = data.dict() + data["end_date"] = ( + data.get("start_date") + if not data.get("end_date") + else data.get("end_date") + ) + serializer = LeaveRequestCreateUpdateSerializer(leave_request, data=data) + if serializer.is_valid(): + leave_request = serializer.save() + with contextlib.suppress(Exception): + notify.send( + request.user.employee_get, + recipient=leave_request.employee_id.employee_work_info.reporting_manager_id.employee_user_id, + verb=f"Leave request updated for {leave_request.employee_id}.", + verb_ar=f"تم تحديث طلب الإجازة لـ {leave_request.employee_id}.", + verb_de=f"Urlaubsantrag aktualisiert für {leave_request.employee_id}.", + verb_es=f"Solicitud de permiso actualizada para {leave_request.employee_id}.", + verb_fr=f"Demande de congé mise à jour pour {leave_request.employee_id}.", + icon="people-circle", + redirect=f"/leave/request-view?id={leave_request.id}", + api_redirect=f"/api/leave/request/{leave_request.id}/", + ) + return Response( + UserLeaveRequestGetSerilaizer( + leave_request, context={"request": request} + ).data, + status=201, + ) + return Response(serializer.errors, status=400) + raise serializers.ValidationError({"error": "Access Denied.."}) + + @manager_permission_required("leave.delete_leaverequest") + def delete(self, request, pk): + leave_request = self.get_leave_request(pk) + if leave_request.status == "requested": + leave_request.delete() + return Response(status=200) + raise serializers.ValidationError({"error": "Access Denied.."}) + + +class CompanyLeaveGetCreateAPIView(APIView): + permission_classes = [IsAuthenticated] + + @method_decorator( + permission_required("leave.view_companyleave", raise_exception=True), + name="dispatch", + ) + def get(self, request): + company_leave = CompanyLeave.objects.all().order_by("-id") + paginator = PageNumberPagination() + page = paginator.paginate_queryset(company_leave, request) + serializer = CompanyLeaveSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + @method_decorator( + permission_required("leave.add_companyleave", raise_exception=True), + name="dispatch", + ) + def post(self, request): + serializer = CompanyLeaveSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + +class CompanyLeaveGetUpdateDeleteAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_company_leave(self, pk): + try: + return CompanyLeave.objects.get(pk=pk) + except CompanyLeave.DoesNotExist as e: + raise serializers.ValidationError(e) + + @method_decorator( + permission_required("leave.view_companyleave", raise_exception=True), + name="dispatch", + ) + def get(self, request, pk): + company_leave = self.get_company_leave(pk) + serializer = CompanyLeaveSerializer(company_leave) + return Response(serializer.data, status=200) + + @method_decorator( + permission_required("leave.change_companyleave", raise_exception=True), + name="dispatch", + ) + def put(self, request, pk): + company_leave = self.get_company_leave(pk) + serializer = CompanyLeaveSerializer(company_leave, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator( + permission_required("leave.delete_companyleave", raise_exception=True), + name="dispatch", + ) + def delete(self, request, pk): + company_leave = self.get_company_leave(pk) + company_leave.delete() + return Response(status=200) + + +class HolidayGetCreateAPIView(APIView): + permission_classes = [IsAuthenticated] + + @method_decorator( + permission_required("leave.view_holiday", raise_exception=True), name="dispatch" + ) + def get(self, request): + holiday = Holiday.objects.all().order_by("-id") + paginator = PageNumberPagination() + page = paginator.paginate_queryset(holiday, request) + serializer = HoildaySerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + @method_decorator( + permission_required("leave.add_holiday", raise_exception=True), name="dispatch" + ) + def post(self, request): + serializer = HoildaySerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + +class HolidayGetUpdateDeleteAPIView(APIView): + + def get_holiday(self, pk): + try: + return Holiday.objects.get(pk=pk) + except Holiday.DoesNotExist as e: + raise serializers.ValidationError(e) + + @method_decorator( + permission_required("leave.view_holiday", raise_exception=True), name="dispatch" + ) + def get(self, request, pk): + holiday = self.get_holiday(pk) + serializer = HoildaySerializer(holiday) + return Response(serializer.data, status=200) + + @method_decorator( + permission_required("leave.change_holiday", raise_exception=True), + name="dispatch", + ) + def put(self, request, pk): + holiday = self.get_holiday(pk) + serializer = HoildaySerializer(holiday, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + @method_decorator( + permission_required("leave.delete_holiday", raise_exception=True), + name="dispatch", + ) + def delete(self, request, pk): + holiday = self.get_holiday(pk) + holiday.delete() + return Response(status=200) + + +class LeaveRequestApproveAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_leave_request(self, pk): + try: + return LeaveRequest.objects.get(pk=pk) + except LeaveRequest.DoesNotExist as e: + raise serializers.ValidationError(e) + + def leave_approve_calculation(self, leave_request, available_leave): + if leave_request.requested_days > available_leave.available_days: + leave = leave_request.requested_days - available_leave.available_days + leave_request.approved_available_days = available_leave.available_days + available_leave.available_days = 0 + available_leave.carryforward_days = ( + available_leave.carryforward_days - leave + ) + + leave_request.approved_carryforward_days = leave + else: + temp = available_leave.available_days + available_leave.available_days = temp - leave_request.requested_days + leave_request.approved_available_days = leave_request.requested_days + available_leave.save() + + def leave_multiple_approve(self, request, leave_request, available_leave): + if request.user.is_superuser: + LeaveRequestConditionApproval.objects.filter( + leave_request_id=leave_request + ).update(is_approved=True) + self.leave_approve_calculation(leave_request, available_leave) + leave_request.status = "approved" + leave_request.save() + else: + conditional_requests = leave_request.multiple_approvals() + approver = [ + manager + for manager in conditional_requests["managers"] + if manager.employee_user_id == request.user + ] + condition_approval = LeaveRequestConditionApproval.objects.filter( + manager_id=approver[0], leave_request_id=leave_request + ).first() + condition_approval.is_approved = True + condition_approval.save() + if approver[0] == conditional_requests["managers"][-1]: + self.leave_approve_calculation(leave_request, available_leave) + leave_request.status = "approved" + leave_request.save() + + @manager_permission_required("leave.change_leaverequest") + def put(self, request, pk): + leave_request = self.get_leave_request(pk) + serializer = LeaveRequestApproveSerializer(leave_request, data=request.data) + if serializer.is_valid(): + available_leave = serializer.validated_data.get("available_leave") + if not leave_request.multiple_approvals(): + self.leave_approve_calculation(leave_request, available_leave) + leave_request.status = "approved" + leave_request.save() + else: + self.leave_multiple_approve(request, leave_request, available_leave) + with contextlib.suppress(Exception): + notify.send( + request.user.employee_get, + recipient=leave_request.employee_id.employee_user_id, + verb="Your Leave request has been approved", + verb_ar="تمت الموافقة على طلب الإجازة الخاص بك", + verb_de="Ihr Urlaubsantrag wurde genehmigt", + verb_es="Se ha aprobado su solicitud de permiso", + verb_fr="Votre demande de congé a été approuvée", + icon="people-circle", + redirect=f"/leave/user-request-view?id={leave_request.id}", + api_redirect=f"/api/leave/user-request/{leave_request.id}", + ) + return Response(status=200) + return Response(serializer.errors, status=400) + + +class LeaveRequestRejectAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_leave_request(self, pk): + try: + return LeaveRequest.objects.get(pk=pk) + except LeaveRequest.DoesNotExist as e: + raise serializers.ValidationError(e) + + def leave_calculation(self, leave_request, employee_id): + leave_type_id = leave_request.leave_type_id + available_leave = AvailableLeave.objects.get( + leave_type_id=leave_type_id, employee_id=employee_id + ) + available_leave.available_days += leave_request.approved_available_days + available_leave.carryforward_days += leave_request.approved_carryforward_days + available_leave.save() + leave_request.approved_available_days = 0 + leave_request.approved_carryforward_days = 0 + leave_request.status = "rejected" + leave_request.save() + + @manager_permission_required("leave.change_leaverequest") + def put(self, request, pk): + leave_request = self.get_leave_request(pk) + employee_id = request.user.employee_get + if leave_request.status != "rejected": + self.leave_calculation(leave_request, employee_id) + with contextlib.suppress(Exception): + notify.send( + request.user.employee_get, + recipient=leave_request.employee_id.employee_user_id, + verb="Your Leave request has been rejected", + verb_ar="تم رفض طلب الإجازة الخاص بك", + verb_de="Ihr Urlaubsantrag wurde abgelehnt", + verb_es="Tu solicitud de permiso ha sido rechazada", + verb_fr="Votre demande de congé a été rejetée", + icon="people-circle", + redirect=f"/leave/user-request-view?id={leave_request.id}", + api_redirect=f"/api/leave/user-request/{leave_request.id}/", + ) + return Response(status=200) + raise serializers.ValidationError("Nothing to reject.") + + +class LeaveRequestCancelAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_leave_request(self, pk): + try: + return LeaveRequest.objects.get(pk=pk) + except LeaveRequest.DoesNotExist as e: + raise serializers.ValidationError(e) + + def put(self, request, pk): + leave_request = self.get_leave_request(pk) + if ( + leave_request.employee_id == request.user.employee_get + and leave_request.status == "approved" + ): + start_date = leave_request.start_date + curr_date = datetime.now().date() + if start_date >= curr_date: + leave_request.status = "cancelled" + leave_request.save() + return Response(status=200) + raise serializers.ValidationError("Nothing to cancel.") + raise serializers.ValidationError("Access Denied.") + + +class LeaveAllocationApproveAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_leave_allocation_request(self, pk): + try: + return LeaveAllocationRequest.objects.get(pk=pk) + except LeaveAllocationRequest.DoesNotExist as e: + raise serializers.ValidationError(e) + + def approve_calculations(self, leave_allocation_request): + available_leave = AvailableLeave.objects.get_or_create( + employee_id=leave_allocation_request.employee_id, + leave_type_id=leave_allocation_request.leave_type_id, + )[0] + available_leave.available_days += leave_allocation_request.requested_days + available_leave.save() + + @manager_permission_required("leave.change_leaveallocationrequest") + def put(self, request, pk): + leave_allocation_request = self.get_leave_allocation_request(pk) + if leave_allocation_request.status == "requested": + self.approve_calculations(leave_allocation_request) + leave_allocation_request.status = "approved" + leave_allocation_request.save() + return Response(status=200) + raise serializers.ValidationError("Access Denied.") + + +class LeaveAllocationRequestRejectAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_leave_allocation_request(self, pk): + try: + return LeaveAllocationRequest.objects.get(pk=pk) + except LeaveAllocationRequest.DoesNotExist as e: + raise serializers.ValidationError(e) + + def reject_calculation(self, leave_allocation_request): + if leave_allocation_request.status == "approved": + leave_type = leave_allocation_request.leave_type_id + requested_days = leave_allocation_request.requested_days + available_leave = AvailableLeave.objects.filter( + leave_type_id=leave_type, + employee_id=leave_allocation_request.employee_id, + ).first() + available_leave.available_days = max( + 0, available_leave.available_days - requested_days + ) + available_leave.save() + + @manager_permission_required("leave.change_leaveallocationrequest") + def put(self, request, pk): + leave_allocation_request = self.get_leave_allocation_request(pk) + if leave_allocation_request.status != "rejected": + self.reject_calculation(leave_allocation_request) + leave_allocation_request.status = "rejected" + leave_allocation_request.save() + return Response(status=200) + raise serializers.ValidationError("Access Denied.") + + +class LeaveRequestBulkApproveDeleteAPIview(APIView): + permission_classes = [IsAuthenticated] + + def get_leave_requests(self, request): + try: + leave_request_ids = request.data.getlist("leave_request_id") + except Exception as e: + raise serializers.ValidationError( + {"leave_request_id": ["This field is required"]} + ) + leave_requests = LeaveRequest.objects.filter(id__in=leave_request_ids).exclude( + status__in=["reject", "cancelled", "approved"] + ) + if leave_requests: + return leave_requests + raise serializers.ValidationError("Nothing to approve") + + def leave_approve_calculation(self, leave_request, available_leave): + if leave_request.requested_days > available_leave.available_days: + leave = leave_request.requested_days - available_leave.available_days + leave_request.approved_available_days = available_leave.available_days + available_leave.available_days = 0 + available_leave.carryforward_days = ( + available_leave.carryforward_days - leave + ) + leave_request.approved_carryforward_days = leave + else: + temp = available_leave.available_days + available_leave.available_days = temp - leave_request.requested_days + leave_request.approved_available_days = leave_request.requested_days + available_leave.save() + + @manager_permission_required("leave.change_leaverequest") + def put(self, request): + leave_requests = self.get_leave_requests(request) + for leave_request in leave_requests: + employee_id = leave_request.employee_id + leave_type_id = leave_request.leave_type_id + available_leave = AvailableLeave.objects.get( + leave_type_id=leave_type_id, employee_id=employee_id + ) + total_available_leave = ( + available_leave.available_days + available_leave.carryforward_days + ) + if total_available_leave >= leave_request.requested_days: + self.leave_approve_calculation(leave_request, available_leave) + leave_request.status = "approved" + leave_request.save() + return Response(status=200) + + @manager_permission_required("leave.delete_leaverequest") + def delete(self, request): + leave_requests = self.get_leave_requests(request) + leave_requests.delete() + return Response(status=200) + + +class EmployeeLeaveAllocationGetCreateAPIView(APIView): + permission_classes = [IsAuthenticated] + filter_backends = [DjangoFilterBackend] + filterset_class = LeaveAllocationRequestFilter + + def get_user(self, request): + user = request.user + if isinstance(user, AnonymousUser): + raise Http404("AnonymousUser") + return user + + def get(self, request): + employee = self.get_user(request).employee_get + allocation_requests = employee.leaveallocationrequest_set.all().order_by("-id") + filterset = self.filterset_class(request.GET, queryset=allocation_requests) + paginator = PageNumberPagination() + field_name = request.GET.get("groupby_field", None) + if field_name: + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, filterset.qs) + page = paginator.paginate_queryset(filterset.qs, request) + serializer = LeaveAllocationRequestGetSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + def post(self, request): + data = request.data + employee_id = self.get_user(request).employee_get.id + if isinstance(data, QueryDict): + data = data.dict() + data["employee_id"] = employee_id + data["created_by"] = employee_id + serializer = LeaveAllocationRequestCreateSerializer(data=data) + if serializer.is_valid(): + serializer.save() + return Response(status=200) + return Response(serializer.errors, status=400) + + +class EmployeeLeaveAllocationUpdateDeleteAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_allocation_request(self, request, pk): + user = request.user + if isinstance(user, AnonymousUser): + raise Http404("AnonymousUser") + try: + allocation_request = LeaveAllocationRequest.objects.get(id=pk) + if allocation_request.employee_id == user.employee_get: + return allocation_request + except LeaveAllocationRequest.DoesNotExist as e: + raise serializers.ValidationError(e) + + def put(self, request, pk): + allocation_request = self.get_allocation_request(request, pk) + if allocation_request.status == "requested": + data = request.data + employee_id = request.user.employee_get.id + if isinstance(data, QueryDict): + data = data.dict() + data["employee_id"] = employee_id + data["created_by"] = employee_id + serializer = LeaveAllocationRequestSerilaizer(allocation_request, data=data) + if serializer.is_valid(): + allocation_request = serializer.save() + return Response( + LeaveAllocationRequestGetSerializer(allocation_request).data, + status=201, + ) + return Response(serializer.errors, status=400) + raise serializers.ValidationError({"error": "Access Denied.."}) + return Response(status=200) + + def delete(self, request, pk): + allocation_request = self.get_allocation_request(request, pk) + if allocation_request.status == "requested": + allocation_request.delete() + return Response(status=200) + raise serializers.ValidationError({"error": "Access Denied.."}) + + +class LeaveRequestedApprovedCountAPIView(APIView): + permission_classes = [IsAuthenticated] + + @manager_permission_required("leave.view_leaverequest") + def get(self, request): + leave_requests = LeaveRequest.objects.all() + multiple_approvals = filter_conditional_leave_request(request) + queryset = ( + filtersubordinates(request, leave_requests, "leave.view_leaverequest") + | multiple_approvals + ) + requested = queryset.filter(status="requested").count() + approved = queryset.filter(status="approved").count() + data = {"requested": requested, "approved": approved} + return Response(data, status=200) + + +class EmployeeAvailableLeaveTypeGetAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_employee(self, pk): + try: + return Employee.objects.get(pk=pk) + except Employee.DoesNotExist as e: + raise serializers.ValidationError(e) + + def get(self, request, pk): + employee = self.get_employee(pk) + available_leave = employee.available_leave.all() + leave_type_ids = available_leave.values_list("leave_type_id", flat=True) + leave_types = LeaveType.objects.filter(id__in=leave_type_ids) + paginator = PageNumberPagination() + page = paginator.paginate_queryset(leave_types, request) + serializer = LeaveTypeAllGetSerializer(page, many=True) + return paginator.get_paginated_response(serializer.data) + + +class LeaveTypeGetPermissionCheckAPIView(APIView): + permission_classes = [IsAuthenticated] + + @method_decorator( + permission_required("leave.add_leavetype", raise_exception=True), + name="dispatch", + ) + def get(self, request): + return Response(status=200) + + +class LeaveAllocationGetPermissionCheckAPIView(APIView): + permission_classes = [IsAuthenticated] + + @manager_permission_required("leave.view_leaveallocationrequest") + def get(self, request): + return Response(status=200) + + +class LeaveRequestGetPermissionCheckAPIView(APIView): + permission_classes = [IsAuthenticated] + + @manager_permission_required("leave.view_leaverequest") + def get(self, request): + return Response(status=200) + + +class LeaveAssignGetPermissionCheckAPIView(APIView): + permission_classes = [IsAuthenticated] + + @method_decorator( + permission_required("leave.view_availableleave", raise_exception=True), + name="dispatch", + ) + def get(self, request): + return Response(status=200) + + +class LeavePermissionCheckAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + leave_type = LeaveTypeGetPermissionCheckAPIView() + leave_allocation = LeaveAllocationGetPermissionCheckAPIView() + leave_request = LeaveRequestGetPermissionCheckAPIView() + leave_assign = LeaveAssignGetPermissionCheckAPIView() + perm_list = [] + try: + if leave_type.get(request).status_code == 200: + perm_list.append("leave_type") + except: + pass + try: + if leave_allocation.get(request).status_code == 200: + perm_list.append("leave_allocation") + except: + pass + try: + if leave_request.get(request).status_code == 200: + perm_list.append("leave_request") + perm_list.append("leave_overview") + except: + pass + try: + if leave_assign.get(request).status_code == 200: + perm_list.append("leave_assign") + except: + pass + return Response({"perm_list": perm_list}, status=200) diff --git a/horilla_api/api_views/notifications/views.py b/horilla_api/api_views/notifications/views.py new file mode 100644 index 000000000..e8e7d5e4f --- /dev/null +++ b/horilla_api/api_views/notifications/views.py @@ -0,0 +1,65 @@ +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated + +from ...api_serializers.notifications.serializers import NotificationSerializer +from rest_framework.pagination import PageNumberPagination + +# Create your views here. + + + +class NotificationView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, type): + if type == 'all': + queryset = request.user.notifications.all() + elif type == 'unread': + queryset = request.user.notifications.unread() + + pagination = PageNumberPagination() + page = pagination.paginate_queryset(queryset, request) + serializer = NotificationSerializer(page, many=True) + return pagination.get_paginated_response(serializer.data) + + +class NotificationReadDelView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, id): + obj = request.user.notifications.filter(id=id).first() + obj.mark_as_read() + serializer = NotificationSerializer(obj) + return Response(serializer.data, status=200) + + def delete(self, request, id): + obj = request.user.notifications.filter(id=id).first() + obj.deleted = True + obj.save() + return Response({"status": "deleted"}, status=200) + + +class NotificationBulkReadDelView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request): + obj = request.user.notifications.all() + obj.mark_all_as_read() + return Response({"status": "marked as read"}, status=200) + + def delete(self, request): + obj = request.user.notifications.all() + obj.mark_all_as_deleted() + return Response({"status": "deleted"}, status=200) + + + +class NotificationBulkDelUnreadMessageView(APIView): + permission_classes = [IsAuthenticated] + + def delete(self, request): + obj = request.user.notifications.unread() + obj.mark_all_as_deleted() + return Response({"status": "deleted"}, status=200) + \ No newline at end of file diff --git a/horilla_api/api_views/payroll/views.py b/horilla_api/api_views/payroll/views.py new file mode 100644 index 000000000..b3dfbbdab --- /dev/null +++ b/horilla_api/api_views/payroll/views.py @@ -0,0 +1,348 @@ +from collections import defaultdict +import gettext +from django.shortcuts import render +from rest_framework.views import APIView + +from base.backends import ConfiguredEmailBackend +from payroll.models.tax_models import TaxBracket +from payroll.threadings.mail import MailSendThread +from payroll.views.views import payslip_pdf +from ... api_serializers.payroll.serializers import AllowanceSerializer, ContractSerializer, DeductionSerializer, LoanAccountSerializer, PayslipSerializer, ReimbursementSerializer, TaxBracketSerializer +from ...api_methods.base.methods import groupby_queryset +from payroll.filters import AllowanceFilter, ContractFilter, DeductionFilter, PayslipFilter +from payroll.models.models import Allowance, Contract, Deduction, LoanAccount, Payslip, Reimbursement +from rest_framework.pagination import PageNumberPagination +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from django.contrib.auth.decorators import permission_required +from django.utils.decorators import method_decorator + + +class PayslipView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request,id=None): + if id: + payslip = Payslip.objects.filter(id=id).first() + if request.user.has_perm("payroll.view_payslip") or payslip.employee_id == request.user.employee_get: + serializer = PayslipSerializer(payslip) + return Response(serializer.data,status=200) + if request.user.has_perm("payroll.view_payslip"): + payslips = Payslip.objects.all() + else: + payslips = Payslip.objects.filter( + employee_id__employee_user_id=request.user + ) + + payslip_filter_queryset = PayslipFilter(request.GET, payslips).qs + # groupby workflow + field_name = request.GET.get("groupby_field", None) + if field_name: + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, payslip_filter_queryset) + pagination = PageNumberPagination() + page = pagination.paginate_queryset(payslip_filter_queryset, request) + serializer = PayslipSerializer(page, many=True) + return pagination.get_paginated_response(serializer.data) + +class PayslipDownloadView(APIView): + + permission_classes = [IsAuthenticated] + + def get(self, request, id): + if request.user.has_perm("payroll.view_payslip"): + return payslip_pdf(request, id) + + if Payslip.objects.filter(id=id, employee_id=request.user.employee_get): + return payslip_pdf(request, id) + else: + raise Response({"error":"You don't have permission"}) + + +class PayslipSendMailView(APIView): + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("payroll.add_payslip", raise_exception=True)) + def post(self, request): + email_backend = ConfiguredEmailBackend() + if not getattr( + email_backend, "dynamic_username_with_display_name", None + ) or not len(email_backend.dynamic_username_with_display_name): + return Response({"error": "Email server is not configured"}, status=400) + + payslip_ids = request.data.get("id", []) + payslips = Payslip.objects.filter(id__in=payslip_ids) + result_dict = defaultdict( + lambda: {"employee_id": None, "instances": [], "count": 0} + ) + + for payslip in payslips: + employee_id = payslip.employee_id + result_dict[employee_id]["employee_id"] = employee_id + result_dict[employee_id]["instances"].append(payslip) + result_dict[employee_id]["count"] += 1 + mail_thread = MailSendThread( + request, result_dict=result_dict, ids=payslip_ids) + mail_thread.start() + return Response({"status": "success"}, status=200) + + +class ContractView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request,id=None): + if id : + contract = Contract.objects.filter(id=id).first() + serializer = ContractSerializer(contract) + return Response(serializer.data,status=200) + if request.user.has_perm("payroll.view_contract"): + contracts = Contract.objects.all() + else: + contracts = Contract.objects.filter( + employee_id=request.user.employee_get) + filter_queryset = ContractFilter(request.GET, contracts).qs + # groupby workflow + field_name = request.GET.get("groupby_field", None) + if field_name: + url = request.build_absolute_uri() + return groupby_queryset(request, url, field_name, filter_queryset) + pagination = PageNumberPagination() + page = pagination.paginate_queryset(filter_queryset, request) + serializer = ContractSerializer(page, many=True) + return pagination.get_paginated_response(serializer.data) + + @method_decorator(permission_required("payroll.add_contract", raise_exception=True)) + def post(self, request): + serializer = ContractSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("payroll.change_contract", raise_exception=True)) + def put(self, request, pk): + contract = Contract.objects.get(id=pk) + serializer = ContractSerializer(instance=contract, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("payroll.delete_contract", raise_exception=True)) + def delete(self, request, pk): + contract = Contract.objects.get(id=pk) + contract.delete() + return Response({"status": "deleted"}, status=200) + + +class AllowanceView(APIView): + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("payroll.view_allowance", raise_exception=True)) + def get(self, request,pk=None): + if pk: + allowance = Allowance.objects.get(id=pk) + serializer = AllowanceSerializer(instance=allowance) + return Response(serializer.data,status=200) + allowance = Allowance.objects.all() + filter_queryset = AllowanceFilter(request.GET, allowance).qs + pagination = PageNumberPagination() + page = pagination.paginate_queryset(filter_queryset, request) + serializer = AllowanceSerializer(page, many=True) + return pagination.get_paginated_response(serializer.data) + + @method_decorator(permission_required("payroll.add_allowance", raise_exception=True)) + def post(self, request): + serializer = AllowanceSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("payroll.change_allowance", raise_exception=True)) + def put(self, request, pk): + contract = Allowance.objects.get(id=pk) + serializer = AllowanceSerializer(instance=contract, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("payroll.delete_allowance", raise_exception=True)) + def delete(self, request, pk): + contract = Allowance.objects.get(id=pk) + contract.delete() + return Response({"status": "deleted"}, status=200) + + +class DeductionView(APIView): + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("payroll.view_deduction", raise_exception=True)) + def get(self, request,pk=None): + if pk: + deduction = Deduction.objects.get(id=pk) + serializer = DeductionSerializer(instance=deduction) + return Response(serializer.data,status=200) + deduction = Deduction.objects.all() + filter_queryset = DeductionFilter(request.GET, deduction).qs + pagination = PageNumberPagination() + page = pagination.paginate_queryset(filter_queryset, request) + serializer = DeductionSerializer(page, many=True) + return pagination.get_paginated_response(serializer.data) + + @method_decorator(permission_required("payroll.add_deduction", raise_exception=True)) + def post(self, request): + serializer = DeductionSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("payroll.change_deduction", raise_exception=True)) + def put(self, request, pk): + contract = Deduction.objects.get(id=pk) + serializer = DeductionSerializer(instance=contract, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("payroll.delete_deduction", raise_exception=True)) + def delete(self, request, pk): + contract = Deduction.objects.get(id=pk) + contract.delete() + return Response({"status": "deleted"}, status=200) + + +class LoanAccountView(APIView): + permission_classes = [IsAuthenticated] + + @method_decorator(permission_required("payroll.add_loanaccount", raise_exception=True)) + def post(self, request): + serializer = LoanAccountSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("payroll.view_loanaccount", raise_exception=True)) + def get(self, request, pk=None): + if pk: + loan_account = LoanAccount.objects.get(id=pk) + serializer = LoanAccountSerializer(instance=loan_account) + return Response(serializer.data, status=200) + loan_accounts = LoanAccount.objects.all() + pagination = PageNumberPagination() + page = pagination.paginate_queryset(loan_accounts, request) + serializer = LoanAccountSerializer(page, many=True) + return pagination.get_paginated_response(serializer.data) + + @method_decorator(permission_required("payroll.change_loanaccount", raise_exception=True)) + def put(self, request, pk): + loan_account = LoanAccount.objects.get(id=pk) + serializer = LoanAccountSerializer(loan_account, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("payroll.delete_loanaccount", raise_exception=True)) + def delete(self, request, pk): + loan_account = LoanAccount.objects.get(id=pk) + loan_account.delete() + return Response(status=200) + + +class ReimbursementView(APIView): + serializer_class = ReimbursementSerializer + permission_classes = [IsAuthenticated] + + def get(self, request, pk=None): + if pk: + reimbursement = Reimbursement.objects.get(id=pk) + serializer = self.serializer_class(reimbursement) + return Response(serializer.data, status=200) + reimbursements = Reimbursement.objects.all() + + if request.user.has_perm("payroll.view_reimbursement"): + reimbursements = Reimbursement.objects.all() + else: + reimbursements = Reimbursement.objects.filter( + employee_id=request.user.employee_get) + pagination = PageNumberPagination() + page = pagination.paginate_queryset(reimbursements, request) + serializer = self.serializer_class(page, many=True) + return pagination.get_paginated_response(serializer.data) + + def post(self, request): + serializer = self.serializer_class( + data=request.data, context={'request': request}) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("payroll.change_reimbursement", raise_exception=True)) + def put(self, request, pk): + reimbursement = Reimbursement.objects.get(id=pk) + serializer = self.serializer_class( + instance=reimbursement, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + @method_decorator(permission_required("payroll.delete_reimbursement", raise_exception=True)) + def delete(self, request, pk): + reimbursement = Reimbursement.objects.get(id=pk) + reimbursement.delete() + return Response(status=200) + +class ReimbusementApproveRejectView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + status = request.data.get('status', None) + amount = request.data.get('amount', None) + amount = eval(request.data.get('amount') + ) if request.data.get('amount') else 0 + amount = max(0, amount) + reimbursement = Reimbursement.objects.filter(id=pk) + if amount: + reimbursement.update(amount=amount) + reimbursement.update(status=status) + return Response({"status": reimbursement.first().status}, status=200) + +class TaxBracketView(APIView): + + def get(self, request, pk=None): + if pk: + tax_bracket = TaxBracket.objects.get(id=pk) + serializer = TaxBracketSerializer(tax_bracket) + return Response(serializer.data, status=200) + tax_brackets = TaxBracket.objects.all() + serializer = TaxBracketSerializer(instance=tax_brackets, many=True) + return Response(serializer.data, status=200) + + def post(self, request): + serializer = TaxBracketSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + def put(self, request, pk): + tax_bracket = TaxBracket.objects.get(id=pk) + serializer = TaxBracketSerializer( + instance=tax_bracket, data=request.data, partial=True) + if serializer.save(): + serializer.save() + return Response(serializer.data, status=200) + return Response(serializer.errors, status=400) + + def delete(self, request, pk): + tax_bracket = TaxBracket.objects.get(id=pk) + tax_bracket.delete() + return Response(status=200) + \ No newline at end of file diff --git a/horilla_api/apps.py b/horilla_api/apps.py new file mode 100644 index 000000000..9d258fba4 --- /dev/null +++ b/horilla_api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HorillaApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'horilla_api' diff --git a/horilla_api/migrations/__init__.py b/horilla_api/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/horilla_api/models.py b/horilla_api/models.py new file mode 100644 index 000000000..71a836239 --- /dev/null +++ b/horilla_api/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/horilla_api/tests.py b/horilla_api/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/horilla_api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/horilla_api/urls.py b/horilla_api/urls.py new file mode 100644 index 000000000..23f9d58c7 --- /dev/null +++ b/horilla_api/urls.py @@ -0,0 +1,13 @@ +from django.urls import path,include + + +urlpatterns = [ + path('auth/', include('horilla_api.api_urls.auth.urls')), + path('asset/', include('horilla_api.api_urls.asset.urls')), + path('base/', include('horilla_api.api_urls.base.urls')), + path('employee/', include('horilla_api.api_urls.employee.urls')), + path('notifications/', include('horilla_api.api_urls.notifications.urls')), + path('payroll/', include('horilla_api.api_urls.payroll.urls')), + path('attendance/', include('horilla_api.api_urls.attendance.urls')), + path('leave/', include('horilla_api.api_urls.leave.urls')), + ] \ No newline at end of file