diff --git a/config/settings/base.py b/config/settings/base.py index 0dc7ada1..a346a065 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/__init__.py b/modules/elm/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/elm/api/__init__.py b/modules/elm/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/elm/api/serializers.py b/modules/elm/api/serializers.py new file mode 100644 index 00000000..e5cd5fe9 --- /dev/null +++ b/modules/elm/api/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from modules.elm.models import Leave + +class LeaveSerializer(serializers.ModelSerializer): + class Meta: + model = Leave + fields = '__all__' diff --git a/modules/elm/api/urls.py b/modules/elm/api/urls.py new file mode 100644 index 00000000..31a8aa4a --- /dev/null +++ b/modules/elm/api/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .views import LeaveViewSet + +router = DefaultRouter() +router.register(r'leaves', LeaveViewSet) + +urlpatterns = [ + path('', include(router.urls)), +] diff --git a/modules/elm/api/views.py b/modules/elm/api/views.py new file mode 100644 index 00000000..ac10cda1 --- /dev/null +++ b/modules/elm/api/views.py @@ -0,0 +1,7 @@ +from rest_framework import viewsets +from modules.elm.models import Leave +from .serializers import LeaveSerializer + +class LeaveViewSet(viewsets.ModelViewSet): + queryset = Leave.objects.all() + serializer_class = LeaveSerializer diff --git a/modules/elm/apps.py b/modules/elm/apps.py new file mode 100644 index 00000000..05a1a96a --- /dev/null +++ b/modules/elm/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + +class ElmConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'modules.elm' + label = 'elm' diff --git a/modules/elm/models/__init__.py b/modules/elm/models/__init__.py new file mode 100644 index 00000000..bc9d9993 --- /dev/null +++ b/modules/elm/models/__init__.py @@ -0,0 +1 @@ +from .leave import Leave diff --git a/modules/elm/models/leave.py b/modules/elm/models/leave.py new file mode 100644 index 00000000..0708fc03 --- /dev/null +++ b/modules/elm/models/leave.py @@ -0,0 +1,39 @@ +from django.db import models + +from core.db.mixins import TimeStampedModel, UserScopedModel + + +class Leave(TimeStampedModel, UserScopedModel): + LEAVE_CATEGORIES = [ + ("CL", "Casual Leave"), + ("RH", "Restricted Holiday"), + ("SCL", "Special Casual Leave"), + ("EL", "Extra Leave"), + ("COL", "Compensatory Off Leave"), + ("VL", "Vacation Leave"), + ] + + REQUEST_STATUS = [ + ("PENDING", "Pending"), + ("APPROVED", "Approved"), + ("REJECTED", "Rejected"), + ] + + leave_category = models.CharField(max_length=3, choices=LEAVE_CATEGORIES) + start_date = models.DateField() + end_date = models.DateField() + reason = models.TextField() + request_status = models.CharField( + max_length=10, + choices=REQUEST_STATUS, + default="PENDING", + ) + + class Meta: + db_table = "elm_leave" + + def __str__(self): + return ( + f"User {self.user_id} - {self.get_leave_category_display()} " + f"from {self.start_date} to {self.end_date}" + ) \ No newline at end of file diff --git a/modules/elm/registry.py b/modules/elm/registry.py new file mode 100644 index 00000000..e7bfe762 --- /dev/null +++ b/modules/elm/registry.py @@ -0,0 +1,41 @@ +"""What the server tells the shell about this module (ADR-0010). + +Nav items carry `required_permission` and the server filters before sending — +the client does zero filtering, so a link a role cannot use never reaches the +browser at all. +""" +MODULE = { + "code": "elm", + "label": "Employee Leave Management", + "icon": "Calendar", + "base_path": "/elm", + "nav_section": "HR", + "sort_order": 20, + "status": "active", +} + +NAV_ITEMS = [ + {"code": "elm.dashboard", "label": "Dashboard", "icon": "Home", "to": "/elm", + "required_permission": "elm.dashboard.view", "sort_order": 10}, + {"code": "elm.leaves", "label": "Leave Requests", "icon": "ClipboardList", + "to": "/elm/leaves", "required_permission": "elm.leave.view", "sort_order": 20}, + {"code": "elm.reports", "label": "Reports", "icon": "ChartBar", "to": "/elm/reports", + "required_permission": "elm.report.view", "sort_order": 30}, +] + +PERMISSIONS = [ + ("elm.dashboard.view", "View ELM dashboard"), + ("elm.leave.view", "View leave requests"), + ("elm.leave.create", "Create leave requests"), + ("elm.leave.approve", "Approve leave requests"), + ("elm.leave.reject", "Reject leave requests"), + ("elm.report.view", "View reports"), +] + +ROLE_GRANTS = { + "staff": ["elm.dashboard.view", "elm.leave.view", "elm.report.view"], + "hr": ["elm.dashboard.view", "elm.leave.view", "elm.leave.create", + "elm.leave.approve", "elm.leave.reject", "elm.report.view"], + "admin": ["elm.dashboard.view", "elm.leave.view", "elm.leave.create", + "elm.leave.approve", "elm.leave.reject", "elm.report.view"], +}