From f153b3fe44543a698a017a0407c2d59085a8483f Mon Sep 17 00:00:00 2001 From: shashank897 Date: Mon, 12 Oct 2026 19:15:10 +0530 Subject: [PATCH] feat(elm): implement employee leave management module with domain state machine, services, api, and client UI --- .importlinter | 17 + client/src/app/registry.ts | 5 + client/src/modules/elm/pages/AdminPage.tsx | 72 ++++ .../src/modules/elm/pages/ApplyLeavePage.tsx | 151 ++++++++ .../src/modules/elm/pages/ApprovalsPage.tsx | 91 +++++ client/src/modules/elm/pages/MyLeavesPage.tsx | 113 ++++++ .../src/modules/elm/pages/SubstitutePage.tsx | 66 ++++ client/src/modules/elm/routes.tsx | 19 + config/settings/base.py | 1 + config/urls.py | 1 + modules/elm/__init__.py | 1 + modules/elm/api/__init__.py | 1 + modules/elm/api/serializers.py | 83 ++++ modules/elm/api/urls.py | 19 + modules/elm/api/views.py | 199 ++++++++++ modules/elm/apps.py | 7 + modules/elm/contracts.py | 78 ++++ modules/elm/domain/__init__.py | 1 + modules/elm/domain/calculations.py | 159 ++++++++ modules/elm/domain/entities.py | 54 +++ modules/elm/domain/state_machine.py | 183 +++++++++ modules/elm/models/__init__.py | 12 + modules/elm/models/balances.py | 74 ++++ modules/elm/models/holidays.py | 36 ++ modules/elm/models/leaves.py | 121 ++++++ modules/elm/registry.py | 144 +++++++ modules/elm/selectors/__init__.py | 1 + modules/elm/selectors/leaves.py | 62 +++ modules/elm/services/__init__.py | 1 + modules/elm/services/applications.py | 155 ++++++++ modules/elm/services/workflows.py | 353 ++++++++++++++++++ modules/elm/services/year_end.py | 101 +++++ modules/elm/tests/__init__.py | 1 + modules/elm/tests/test_domain.py | 111 ++++++ 34 files changed, 2493 insertions(+) create mode 100644 client/src/modules/elm/pages/AdminPage.tsx create mode 100644 client/src/modules/elm/pages/ApplyLeavePage.tsx create mode 100644 client/src/modules/elm/pages/ApprovalsPage.tsx create mode 100644 client/src/modules/elm/pages/MyLeavesPage.tsx create mode 100644 client/src/modules/elm/pages/SubstitutePage.tsx create mode 100644 client/src/modules/elm/routes.tsx create mode 100644 modules/elm/__init__.py create mode 100644 modules/elm/api/__init__.py create mode 100644 modules/elm/api/serializers.py create mode 100644 modules/elm/api/urls.py create mode 100644 modules/elm/api/views.py create mode 100644 modules/elm/apps.py create mode 100644 modules/elm/contracts.py create mode 100644 modules/elm/domain/__init__.py create mode 100644 modules/elm/domain/calculations.py create mode 100644 modules/elm/domain/entities.py create mode 100644 modules/elm/domain/state_machine.py create mode 100644 modules/elm/models/__init__.py create mode 100644 modules/elm/models/balances.py create mode 100644 modules/elm/models/holidays.py create mode 100644 modules/elm/models/leaves.py create mode 100644 modules/elm/registry.py create mode 100644 modules/elm/selectors/__init__.py create mode 100644 modules/elm/selectors/leaves.py create mode 100644 modules/elm/services/__init__.py create mode 100644 modules/elm/services/applications.py create mode 100644 modules/elm/services/workflows.py create mode 100644 modules/elm/services/year_end.py create mode 100644 modules/elm/tests/__init__.py create mode 100644 modules/elm/tests/test_domain.py diff --git a/.importlinter b/.importlinter index d964dc3c..ac2796ba 100644 --- a/.importlinter +++ b/.importlinter @@ -13,6 +13,7 @@ name = Modules may reach each other ONLY through contracts.py type = forbidden source_modules = modules.placement + modules.elm modules.directory modules.accesscontrol forbidden_modules = @@ -21,6 +22,11 @@ forbidden_modules = modules.placement.selectors modules.placement.api modules.placement.domain + modules.elm.models + modules.elm.services + modules.elm.selectors + modules.elm.api + modules.elm.domain modules.directory.models modules.directory.services modules.directory.api @@ -38,6 +44,7 @@ type = forbidden source_modules = # Every module's domain, not just the first one written. modules.placement.domain + modules.elm.domain forbidden_modules = django rest_framework @@ -66,3 +73,13 @@ layers = modules.placement.selectors modules.placement.models modules.placement.domain + +[importlinter:contract:elm-layers] +name = Layering inside elm +type = layers +layers = + modules.elm.api + modules.elm.services + modules.elm.selectors + modules.elm.models + modules.elm.domain diff --git a/client/src/app/registry.ts b/client/src/app/registry.ts index e76d32bf..62a76ddf 100644 --- a/client/src/app/registry.ts +++ b/client/src/app/registry.ts @@ -17,4 +17,9 @@ export const MODULE_REGISTRY: Record = { basePath: "/placement", load: () => import("../modules/placement/routes"), }, + elm: { + code: "elm", + basePath: "/elm", + load: () => import("../modules/elm/routes"), + }, }; diff --git a/client/src/modules/elm/pages/AdminPage.tsx b/client/src/modules/elm/pages/AdminPage.tsx new file mode 100644 index 00000000..8e59c34f --- /dev/null +++ b/client/src/modules/elm/pages/AdminPage.tsx @@ -0,0 +1,72 @@ +import React from "react"; +import { + Card, + Title, + Table, + Badge, + Button, + Stack, + Text, + Group, + TextInput, + Select, +} from "@mantine/core"; + +export default function AdminPage() { + const holidays = [ + { date: "2026-01-26", name: "Republic Day", type: "closed" }, + { date: "2026-03-25", name: "Holi", type: "closed" }, + { date: "2026-04-14", name: "Ambedkar Jayanti", type: "restricted" }, + { date: "2026-08-15", name: "Independence Day", type: "closed" }, + { date: "2026-10-02", name: "Gandhi Jayanti", type: "closed" }, + ]; + + return ( + + Leave Administration & Calendars + + Maintain annual holiday calendars, publish Restricted Holidays (RH), and execute year-end processing. + + + + + Published Holiday Calendar (2026) + + + + + + + Date + Holiday Name + Type + + + + {holidays.map((h) => ( + + {h.date} + {h.name} + + + {h.type === "closed" ? "Closed Holiday" : "Restricted Holiday (RH)"} + + + + ))} + +
+
+ + + Year-End Leave Ledger Processing (EVT-01) + + Lapses unused CL/RH/SCL, converts Faculty VL to EL at 2:1 ratio, and carries forward EL/COL to next calendar year. + + + +
+ ); +} diff --git a/client/src/modules/elm/pages/ApplyLeavePage.tsx b/client/src/modules/elm/pages/ApplyLeavePage.tsx new file mode 100644 index 00000000..20819a26 --- /dev/null +++ b/client/src/modules/elm/pages/ApplyLeavePage.tsx @@ -0,0 +1,151 @@ +import React, { useState } from "react"; +import { + Card, + Title, + TextInput, + Select, + Button, + Stack, + Text, + Checkbox, + Group, + Textarea, + Notification, +} from "@mantine/core"; + +export default function ApplyLeavePage() { + const [category, setCategory] = useState("CL"); + const [startDate, setStartDate] = useState(""); + const [endDate, setEndDate] = useState(""); + const [isHalfDay, setIsHalfDay] = useState(false); + const [halfDayPeriod, setHalfDayPeriod] = useState("first_half"); + const [reason, setReason] = useState(""); + const [leavingStation, setLeavingStation] = useState(false); + const [stationAddress, setStationAddress] = useState(""); + const [emergencyPhone, setEmergencyPhone] = useState(""); + const [substituteId, setSubstituteId] = useState(""); + const [submitted, setSubmitted] = useState(false); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setSubmitted(true); + }; + + return ( + + Apply for Leave + + Submit a new leave application with role-based routing and substitute nomination. + + + {submitted && ( + setSubmitted(false)}> + Your leave application has been submitted for review. + + )} + + +
+ + + )} + + )} + +