diff --git a/config/settings/base.py b/config/settings/base.py index 8d47439d..57393af2 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -90,6 +90,7 @@ def env_list(name, default=None): # See docs/03-platform/module-authoring-guide.md to add one. DOMAIN_MODULES = [ "modules.placement", + "modules.elm", ] INSTALLED_APPS = (DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS diff --git a/config/urls.py b/config/urls.py index 33f7835b..a9f602e7 100644 --- a/config/urls.py +++ b/config/urls.py @@ -16,6 +16,7 @@ path("api/v1/", include("fusion_auth.urls")), path("api/v1/directory/", include("modules.directory.api.urls")), path("api/v1/placement/", include("modules.placement.api.urls")), + path("api/v1/elm/", include("modules.elm.api.urls")), path("api/schema/", SpectacularAPIView.as_view(), name="schema"), path("api/docs/", SpectacularSwaggerView.as_view(url_name="schema"), name="docs"), ] diff --git a/modules/elm/api/serializers.py b/modules/elm/api/serializers.py new file mode 100644 index 00000000..6a74466b --- /dev/null +++ b/modules/elm/api/serializers.py @@ -0,0 +1,19 @@ +from rest_framework import serializers + +from modules.elm.models import LeaveApplication + + +class LeaveApplicationSerializer(serializers.ModelSerializer): + class Meta: + model = LeaveApplication + fields = ( + "id", "category", "start_date", "end_date", "reason", + "status", "applied_at", + ) + read_only_fields = ("id", "status", "applied_at") + + +class LeaveApplicationCreateSerializer(serializers.ModelSerializer): + class Meta: + model = LeaveApplication + fields = ("category", "start_date", "end_date", "reason") \ No newline at end of file diff --git a/modules/elm/api/urls.py b/modules/elm/api/urls.py new file mode 100644 index 00000000..59c62190 --- /dev/null +++ b/modules/elm/api/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from modules.elm.api.views import LeaveApplicationListCreateView + +urlpatterns = [ + path("leave-applications", LeaveApplicationListCreateView.as_view(), + name="elm-leave-applications"), +] \ No newline at end of file diff --git a/modules/elm/api/views.py b/modules/elm/api/views.py new file mode 100644 index 00000000..86f7cfbc --- /dev/null +++ b/modules/elm/api/views.py @@ -0,0 +1,31 @@ +from rest_framework import generics + +from fusion_auth.permissions import HasPermission +from modules.elm.api.serializers import ( + LeaveApplicationCreateSerializer, + LeaveApplicationSerializer, +) +from modules.elm.models import LeaveApplication + + +class LeaveApplicationListCreateView(generics.ListCreateAPIView): + def get_queryset(self): + return LeaveApplication.objects.filter( + user_id=self.request.principal.user_id, + ) + + def get_serializer_class(self): + if self.request.method == "POST": + return LeaveApplicationCreateSerializer + return LeaveApplicationSerializer + + def get_permissions(self): + permission = ( + "elm.leave_application.apply" + if self.request.method == "POST" + else "elm.leave_application.view_self" + ) + return [HasPermission(permission)()] + + def perform_create(self, serializer): + serializer.save(user_id=self.request.principal.user_id) \ No newline at end of file diff --git a/modules/elm/apps.py b/modules/elm/apps.py new file mode 100644 index 00000000..b628f9a4 --- /dev/null +++ b/modules/elm/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class ElmConfig(AppConfig): + name = "modules.elm" + label = "elm" + verbose_name = "Employee Leave Management" \ No newline at end of file diff --git a/modules/elm/models.py b/modules/elm/models.py new file mode 100644 index 00000000..8f82972f --- /dev/null +++ b/modules/elm/models.py @@ -0,0 +1,35 @@ +from django.db import models + +from core.db.mixins import TimeStampedModel + + +class LeaveApplication(TimeStampedModel): + class Category(models.TextChoices): + CASUAL = "casual", "Casual Leave" + MEDICAL = "medical", "Medical Leave" + SPECIAL_CASUAL = "special_casual", "Special Casual Leave" + EARNED = "earned", "Earned Leave" + COMPENSATORY = "compensatory", "Compensatory Off Leave" + VACATION = "vacation", "Vacation Leave" + + class Status(models.TextChoices): + PENDING = "pending", "Pending" + APPROVED = "approved", "Approved" + REJECTED = "rejected", "Rejected" + + user_id = models.BigIntegerField(db_index=True) + category = models.CharField(max_length=32, choices=Category.choices) + start_date = models.DateField() + end_date = models.DateField() + reason = models.TextField() + status = models.CharField(max_length=16, choices=Status.choices, + default=Status.PENDING) + applied_at = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ["-applied_at"] + indexes = [models.Index(fields=["user_id", "-applied_at"], + name="elm_user_applied_idx")] + + def __str__(self) -> str: + return f"Leave application {self.pk}" \ No newline at end of file diff --git a/modules/elm/registry.py b/modules/elm/registry.py new file mode 100644 index 00000000..1a88b1e2 --- /dev/null +++ b/modules/elm/registry.py @@ -0,0 +1,38 @@ +MODULE = { + "code": "elm", + "label": "Employee Leave Management", + "icon": "FaCalendarAlt", + "base_path": "/elm", + "nav_section": "People", + "sort_order": 30, + "status": "active", +} + +NAV_ITEMS = [ + { + "code": "elm.leave_applications", + "label": "Leave Applications", + "icon": "FaCalendarAlt", + "to": "/elm/leave-applications", + "required_permission": "elm.leave_application.view_self", + "sort_order": 10, + }, +] + +PERMISSIONS = [ + ("elm.leave_application.view_self", "View own leave applications"), + ("elm.leave_application.apply", "Submit a leave application"), + ("elm.leave_application.manage", "Manage employee leave applications"), +] + +ROLE_GRANTS = { + "employee": [ + "elm.leave_application.view_self", + "elm.leave_application.apply", + ], + "hr": [ + "elm.leave_application.view_self", + "elm.leave_application.apply", + "elm.leave_application.manage", + ], +} \ No newline at end of file