diff --git a/FusionIIIT/Fusion/__init__.py b/FusionIIIT/Fusion/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/FusionIIIT/applications/__init__.py b/FusionIIIT/applications/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/FusionIIIT/applications/central_mess/migrations/0001_initial.py b/FusionIIIT/applications/central_mess/migrations/0001_initial.py index 7e80bedf5..bbac3081f 100644 --- a/FusionIIIT/applications/central_mess/migrations/0001_initial.py +++ b/FusionIIIT/applications/central_mess/migrations/0001_initial.py @@ -149,17 +149,6 @@ class Migration(migrations.Migration): ('student_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='academic_information.student')), ], ), - migrations.CreateModel( - name='Payments', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('amount_paid', models.IntegerField(default=0)), - ('payment_month', models.CharField(default=applications.central_mess.models.current_month, max_length=20)), - ('payment_year', models.IntegerField(default=applications.central_mess.models.current_year)), - ('payment_date', models.DateField(default=datetime.date(2024, 6, 19))), - ('student_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='academic_information.student')), - ], - ), migrations.CreateModel( name='Mess_minutes', fields=[ diff --git a/FusionIIIT/applications/health_center/models.py b/FusionIIIT/applications/health_center/models.py index 7f46307f0..a5b5ca3d5 100644 --- a/FusionIIIT/applications/health_center/models.py +++ b/FusionIIIT/applications/health_center/models.py @@ -4,9 +4,9 @@ from django.contrib.auth.models import User from applications.globals.models import ExtraInfo -from applications.hr2.models import EmpDependents # Create your models here. +# Note: EmpDependents model (previously imported from hr2) does not exist - removed from imports class Constants: DAYS_OF_WEEK = ( diff --git a/FusionIIIT/applications/health_center/utils.py b/FusionIIIT/applications/health_center/utils.py index 220efea76..88eac0d15 100644 --- a/FusionIIIT/applications/health_center/utils.py +++ b/FusionIIIT/applications/health_center/utils.py @@ -18,7 +18,7 @@ from django.shortcuts import get_object_or_404 from django.db.models import Q from applications.globals.models import ExtraInfo -from applications.hr2.models import EmpDependents +# from applications.hr2.models import EmpDependents # Model does not exist def datetime_handler(date): ''' @@ -463,21 +463,23 @@ def compounder_view_handler(request): data = {'status': status, 'stock': stock} return JsonResponse(data) elif 'user_for_dependents' in request.POST: - user = request.POST.get('user_for_dependents') - if not User.objects.filter(username__iexact = user).exists(): - return JsonResponse({"status":-1}) - user_id = User.objects.get(username__iexact = user) - info = ExtraInfo.objects.get(user = user_id) - dep_info = EmpDependents.objects.filter(extra_info = info) - dep=[] - for d in dep_info: - obj={} - obj['name'] = d.name - obj['relation'] = d.relationship - dep.append(obj) - if(len(dep) == 0) : - return JsonResponse({'status':-2}) - return JsonResponse({'status':1,'dep':dep}) + # EmpDependents model does not exist - functionality disabled + return JsonResponse({"status":-2}) + # user = request.POST.get('user_for_dependents') + # if not User.objects.filter(username__iexact = user).exists(): + # return JsonResponse({"status":-1}) + # user_id = User.objects.get(username__iexact = user) + # info = ExtraInfo.objects.get(user = user_id) + # dep_info = EmpDependents.objects.filter(extra_info = info) + # dep=[] + # for d in dep_info: + # obj={} + # obj['name'] = d.name + # obj['relation'] = d.relationship + # dep.append(obj) + # if(len(dep) == 0) : + # return JsonResponse({'status':-2}) + # return JsonResponse({'status':1,'dep':dep}) elif 'prescribe_b' in request.POST: user_id = request.POST.get('user') doctor_id = request.POST.get('doctor') diff --git a/FusionIIIT/applications/hr2.zip b/FusionIIIT/applications/hr2.zip new file mode 100644 index 000000000..42181bcbb Binary files /dev/null and b/FusionIIIT/applications/hr2.zip differ diff --git a/FusionIIIT/applications/hr2/__init__.py b/FusionIIIT/applications/hr2/__init__.py index e69de29bb..5fb708b8f 100644 --- a/FusionIIIT/applications/hr2/__init__.py +++ b/FusionIIIT/applications/hr2/__init__.py @@ -0,0 +1 @@ +# HR2 Module - Leave, Appraisal, LTC, and CPDA Management diff --git a/FusionIIIT/applications/hr2/a.py b/FusionIIIT/applications/hr2/a.py deleted file mode 100644 index a308ef3ad..000000000 --- a/FusionIIIT/applications/hr2/a.py +++ /dev/null @@ -1,75 +0,0 @@ -def reverse_ltc_pre_processing(data): - reversed_data = {} - - # Copying over simple key-value pairs - simple_keys = [ - 'block_year', 'pf_no', 'basic_pay_salary', 'name', 'designation', 'department_info', - 'leave_availability', 'leave_start_date', 'leave_end_date', 'date_of_leave_for_family', - 'nature_of_leave', 'purpose_of_leave', 'hometown_or_not', 'place_of_visit', - 'address_during_leave', 'amount_of_advance_required', 'certified_family_dependents', - 'certified_advance', 'adjusted_month', 'date', 'phone_number_for_contact' - ] - for key in simple_keys: - value = data[key] - reversed_data[key] = value if value != 'None' else '' - - # Reversing array-like values - reversed_data['details_of_family_members_already_done'] = data['details_of_family_members_already_done'].split(',') - - family_members_about_to_avail = data['family_members_about_to_avail'].split(',') - for index, value in enumerate(family_members_about_to_avail): - family_members_about_to_avail[index] = value if value != 'None' else '' - - reversed_data['info_1_1'] = family_members_about_to_avail[0] - reversed_data['info_1_2'] = family_members_about_to_avail[1] - reversed_data['info_1_3'] = family_members_about_to_avail[2] - reversed_data['info_2_1'] = family_members_about_to_avail[3] - reversed_data['info_2_2'] = family_members_about_to_avail[4] - reversed_data['info_2_3'] = family_members_about_to_avail[5] - reversed_data['info_3_1'] = family_members_about_to_avail[6] - reversed_data['info_3_2'] = family_members_about_to_avail[7] - reversed_data['info_3_3'] = family_members_about_to_avail[8] - - # Reversing details_of_dependents - details_of_dependents = data['details_of_dependents'].split(',') - for i in range(1, 7): - for j in range(1, 4): - key = f'd_info_{i}_{j}' - value = details_of_dependents.pop(0) - reversed_data[key] = value if value != 'None' else '' - - return reversed_data - -# Sample data -data = { - 'block_year': '232', 'pf_no': '4324', 'basic_pay_salary': '324', 'name': 'sdf', 'designation': 'fds', - 'department_info': 'dfs', 'leave_availability': 'True', 'leave_start_date': '2024-03-13', - 'leave_end_date': '2024-03-17', 'date_of_leave_for_family': '2024-03-16', 'nature_of_leave': 'erds', - 'purpose_of_leave': 'fds', 'hometown_or_not': 'True', 'place_of_visit': 'fds', 'address_during_leave': 'dfsfsdf', - 'details_of_family_members_already_done': 'fds,dfs,dfs', 'family_members_about_to_avail': '1,dfsf,21,2,dsf,23,3,dfs,12', - 'details_of_dependents': '1,ds,12,2,sds,2,3,ds,13,None,None,None,None,None,None,None,None,None', 'amount_of_advance_required': '1221', - 'certified_family_dependents': '213', 'certified_advance': '213', 'adjusted_month': '213', 'date': '2024-03-15', - 'phone_number_for_contact': '21313123132' -} - -# Reverse processing -reversed_data = reverse_ltc_pre_processing(data) -print(reversed_data) - - - -{'block_year': 232, 'pf_no': None, 'basic_pay_salary': 324, 'name': 'sdf', 'designation': 'fds', - 'department_info': 'dfs', 'leave_availability': True, 'leave_start_date': datetime.date(2024, 3, 13), - 'leave_end_date': datetime.date(2024, 3, 17), 'date_of_leave_for_family': datetime.date(2024, 3, 16), - 'nature_of_leave': 'erds', 'purpose_of_leave': 'fds', 'hometown_or_not': True, 'place_of_visit': 'fds', - 'address_during_leave': 'dfsfsdf', 'amount_of_advance_required': 1221, 'certified_family_dependents': '213', - 'certified_advance': 213, 'adjusted_month': '213', 'date': datetime.date(2024, 3, 15), - 'phone_number_for_contact': 21313123132, - 'details_of_family_members_already_done': ['fds', 'dfs', 'dfs'], - 'info_1_1': '1', 'info_1_2': 'dfsf', 'info_1_3': '21', 'info_2_1': - '2', 'info_2_2': 'dsf', 'info_2_3': '23', 'info_3_1': '3', 'info_3_2': - 'dfs', 'info_3_3': '12', 'd_info_1_1': '1', 'd_info_1_2': 'ds', 'd_info_1_3': - '12', 'd_info_2_1': '2', 'd_info_2_2': 'sds', 'd_info_2_3': '2', 'd_info_3_1': '3', - 'd_info_3_2': 'ds', 'd_info_3_3': '13', 'd_info_4_1': '', 'd_info_4_2': '', 'd_info_4_3': '', - - 'd_info_5_1': '', 'd_info_5_2': '', 'd_info_5_3': '', 'd_info_6_1': '', 'd_info_6_2': '', 'd_info_6_3': ''} diff --git a/FusionIIIT/applications/hr2/admin.py b/FusionIIIT/applications/hr2/admin.py index 3c9b8379a..3b6af051f 100644 --- a/FusionIIIT/applications/hr2/admin.py +++ b/FusionIIIT/applications/hr2/admin.py @@ -1,18 +1,23 @@ from django.contrib import admin +from .models import ( + Employee, EmpConfidentialDetails, EmpDependents, ForeignService, + EmpAppraisalForm, WorkAssignemnt, LTCform, CPDAAdvanceform, + CPDAReimbursementform, LeaveForm, LeaveClaim, LeaveBalance, + LeavePerYear, Appraisalform +) -from .models import * -# from .models import CPDAReimbursementform -# Register your models here. - +# Register existing models admin.site.register(Employee) admin.site.register(EmpConfidentialDetails) admin.site.register(EmpDependents) admin.site.register(ForeignService) admin.site.register(EmpAppraisalForm) admin.site.register(WorkAssignemnt) -admin.site.register(LeaveBalance) -admin.site.register(LeaveForm) admin.site.register(LTCform) -admin.site.register(Appraisalform) -admin.site.register(CPDAAdvanceform) -admin.site.register(CPDAReimbursementform) \ No newline at end of file +admin.site.register(CPDAAdvanceform) +admin.site.register(CPDAReimbursementform) +admin.site.register(LeaveForm) +admin.site.register(LeaveClaim) +admin.site.register(LeaveBalance) +admin.site.register(LeavePerYear) +admin.site.register(Appraisalform) \ No newline at end of file diff --git a/FusionIIIT/applications/hr2/api/__init__.py b/FusionIIIT/applications/hr2/api/__init__.py new file mode 100644 index 000000000..7f76b8ef9 --- /dev/null +++ b/FusionIIIT/applications/hr2/api/__init__.py @@ -0,0 +1 @@ +# HR2 API Module diff --git a/FusionIIIT/applications/hr2/api/form_views.py b/FusionIIIT/applications/hr2/api/form_views.py deleted file mode 100644 index 984e1fbaf..000000000 --- a/FusionIIIT/applications/hr2/api/form_views.py +++ /dev/null @@ -1,546 +0,0 @@ -from .serializers import LTC_serializer, CPDAAdvance_serializer, Appraisal_serializer, CPDAReimbursement_serializer, Leave_serializer, LeaveBalanace_serializer -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status -# from rest_framework.decorators import permission_classes, api_view -from rest_framework.permissions import IsAuthenticated -from applications.hr2.models import LTCform, CPDAAdvanceform, CPDAReimbursementform, LeaveForm, Appraisalform, LeaveBalance -from django.contrib.auth import get_user_model -from django.core.exceptions import MultipleObjectsReturned -from applications.filetracking.sdk.methods import * -from applications.globals.models import Designation, HoldsDesignation, ExtraInfo -from applications.filetracking.models import * -# from django.contrib.auth.models import User - - -class LTC(APIView): - serializer_class = LTC_serializer - permission_classes = (IsAuthenticated, ) - - def post(self, request): - print("hello") - user_info = request.data[1] - print(request.data[1]) - serializer = self.serializer_class(data=request.data[0]) - if serializer.is_valid(): - serializer.save() - fileId = create_file(uploader=user_info['uploader_name'], uploader_designation=user_info['uploader_designation'], receiver=user_info['receiver_name'], - receiver_designation=user_info['receiver_designation'], src_module="HR", src_object_id=str(serializer.data['id']), file_extra_JSON={"type": "LTC"}, attached_file=None) - # forwarded = forward_file(file_id=fileId, receiver=user_info['receiver_name'], receiver_designation=user_info['receiver_designation'], - # remarks="Forwarded to Receiver Inbox", file_extra_JSON={"type": "LTC"}) - return Response(serializer.data, status=status.HTTP_200_OK) - else: - print(serializer.errors) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def get(self, request, *args, **kwargs): - pk = request.query_params.get("name") - try: - forms = LTCform.objects.get(created_by=pk) - serializer = self.serializer_class(forms, many=False) - except MultipleObjectsReturned: - forms = LTCform.objects.filter(created_by=pk) - serializer = self.serializer_class(forms, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - - def put(self, request, *args, **kwargs): - pk = request.query_params.get("id") - receiver = request.data[0] - # send_to = receiver['receiver'] - # receiver_value = User.objects.get(username=send_to) - # receiver_value_designation = HoldsDesignation.objects.filter( - # user=receiver_value) - # lis = list(receiver_value_designation) - # obj = lis[0].designation - form = LTCform.objects.get(id=pk) - serializer = self.serializer_class(form, data=request.data[1]) - if serializer.is_valid(): - serializer.save() - forward_file(file_id=receiver['file_id'], receiver=receiver['receiver'], receiver_designation=receiver['receiver_designation'], - remarks=receiver['remarks'], file_extra_JSON=receiver['file_extra_JSON']) - return Response(serializer.data, status=status.HTTP_200_OK) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def delete(self, request, *args, **kwargs): - id = request.query_params.get("id") - is_archived = archive_file(file_id=id) - if (is_archived): - return Response(status=status.HTTP_200_OK) - else: - return Response(status=status.HTTP_400_BAD_REQUEST) - - -class FormManagement(APIView): - permission_classes = (IsAuthenticated, ) - - def get(self, request, *args, **kwargs): - username = request.query_params.get("username") - designation = request.query_params.get("designation") - inbox = view_inbox(username=username, - designation=designation, src_module="HR") - print(inbox) - return Response(inbox, status=status.HTTP_200_OK) - - def post(self, request, *args, **kwargs): - username = request.data['receiver'] - receiver_value = User.objects.get(username=username) - receiver_value_designation = HoldsDesignation.objects.filter( - user=receiver_value) - lis = list(receiver_value_designation) - obj = lis[0].designation - forward_file(file_id=request.data['file_id'], receiver=request.data['receiver'], receiver_designation=request.data['receiver_designation'], - remarks=request.data['remarks'], file_extra_JSON=request.data['file_extra_JSON']) - return Response(status=status.HTTP_200_OK) - - -class CPDAAdvance(APIView): - serializer_class = CPDAAdvance_serializer - permission_classes = (IsAuthenticated, ) - - def post(self, request): - print(request.data[0]) - user_info = request.data[1] - # receiver_value = User.objects.get(username=user_info['receiver_name']) - # receiver_value_designation = HoldsDesignation.objects.filter( - # user=receiver_value) - # lis = list(receiver_value_designation) - # obj = lis[0].designation - serializer = self.serializer_class(data=request.data[0]) - if serializer.is_valid(): - serializer.save() - print('1') - fileId = create_file(uploader=user_info['uploader_name'], uploader_designation=user_info['uploader_designation'], receiver=user_info['receiver_name'], - receiver_designation=user_info['receiver_designation'], src_module="HR", src_object_id=str(serializer.data['id']), file_extra_JSON={"type": "CPDAAdvance"}, attached_file=None) - # forwarded = forward_file(file_id=fileId, receiver=user_info['receiver_name'], receiver_designation=user_info['receiver_designation'], - # remarks="Forwarded to Receiver Inbox", file_extra_JSON={"type": "CPDAAdvance"}) - return Response(serializer.data, status=status.HTTP_200_OK) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def get(self, request, *args, **kwargs): - pk = request.query_params.get("name") - try: - forms = CPDAAdvanceform.objects.get(created_by=pk) - serializer = self.serializer_class(forms, many=False) - except MultipleObjectsReturned: - forms = CPDAAdvanceform.objects.filter(created_by=pk) - serializer = self.serializer_class(forms, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - - def put(self, request, *args, **kwargs): - pk = request.query_params.get("id") - receiver = request.data[0] - print(request.data) - send_to = receiver['receiver'] - print(send_to) - receiver_value = User.objects.get(username=send_to) - receiver_value_designation = HoldsDesignation.objects.filter( - user=receiver_value) - lis = list(receiver_value_designation) - obj = lis[0].designation - form = CPDAAdvanceform.objects.get(id=pk) - serializer = self.serializer_class(form, data=request.data[1]) - if serializer.is_valid(): - serializer.save() - forward_file(file_id=receiver['file_id'], receiver=receiver['receiver'], receiver_designation=receiver['receiver_designation'], - remarks=receiver['remarks'], file_extra_JSON=receiver['file_extra_JSON']) - return Response(serializer.data, status=status.HTTP_200_OK) - else: - print(serializer.errors) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def delete(self, request, *args, **kwargs): - id = request.query_params.get("id") - is_archived = archive_file(file_id=id) - if (is_archived): - return Response(status=status.HTTP_200_OK) - else: - return Response(status=status.HTTP_400_BAD_REQUEST) - - -class CPDAReimbursement(APIView): - serializer_class = CPDAReimbursement_serializer - permission_classes = (IsAuthenticated, ) - - def post(self, request): - user_info = request.data[1] - serializer = self.serializer_class(data=request.data[0]) - if serializer.is_valid(): - serializer.save() - fileId = create_file(uploader=user_info['uploader_name'], uploader_designation=user_info['uploader_designation'], receiver=user_info['receiver_name'], - receiver_designation=user_info['receiver_designation'], src_module="HR", src_object_id=str(serializer.data['id']), file_extra_JSON={"type": "CPDAReimbursement"}, attached_file=None) - # forwarded = forward_file(file_id=fileId, receiver=user_info['receiver_name'], receiver_designation=user_info['receiver_designation'], - # remarks="Forwarded to Receiver Inbox", file_extra_JSON={"type": "CPDAReimbursement"}) - return Response(serializer.data, status=status.HTTP_200_OK) - else: - print(serializer.errors) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def get(self, request, *args, **kwargs): - pk = request.query_params.get("name") - try: - forms = CPDAReimbursementform.objects.get(created_by=pk) - serializer = self.serializer_class(forms, many=False) - except MultipleObjectsReturned: - forms = CPDAReimbursementform.objects.filter(created_by=pk) - serializer = self.serializer_class(forms, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - - def put(self, request, *args, **kwargs): - pk = request.query_params.get("id") - print(request.data) - receiver = request.data[0] - # send_to = receiver['receiver'] - # receiver_value = User.objects.get(username=send_to) - # receiver_value_designation = HoldsDesignation.objects.filter( - # user=receiver_value) - # lis = list(receiver_value_designation) - # obj = lis[0].designation - form = CPDAReimbursementform.objects.get(id=pk) - serializer = self.serializer_class(form, data=request.data[1]) - if serializer.is_valid(): - serializer.save() - forward_file(file_id=receiver['file_id'], receiver=receiver['receiver'], receiver_designation=receiver['receiver_designation'], - remarks=receiver['remarks'], file_extra_JSON=receiver['file_extra_JSON']) - return Response(serializer.data, status=status.HTTP_200_OK) - else: - print(serializer.errors) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def delete(self, request, *args, **kwargs): - id = request.query_params.get("id") - is_archived = archive_file(file_id=id) - if (is_archived): - return Response(status=status.HTTP_200_OK) - else: - return Response(status=status.HTTP_400_BAD_REQUEST) - - -class Leave(APIView): - serializer_class = Leave_serializer - permission_classes = (IsAuthenticated, ) - - def post(self, request): - user_info = request.data[1] - serializer = self.serializer_class(data=request.data[0]) - if serializer.is_valid(): - serializer.save() - fileId = create_file(uploader=user_info['uploader_name'], uploader_designation=user_info['uploader_designation'], receiver=user_info['receiver_name'], - receiver_designation=user_info['receiver_designation'], src_module="HR", src_object_id=str(serializer.data['id']), file_extra_JSON={"type": "Leave"}, attached_file=None) - # forwarded = forward_file(file_id=fileId, receiver=user_info['receiver_name'], receiver_designation=user_info['receiver_designation'], - # remarks="Forwarded to Receiver Inbox", file_extra_JSON={"type": "Leave"}) - return Response(serializer.data, status=status.HTTP_200_OK) - else: - print(serializer.errors) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def get(self, request, *args, **kwargs): - pk = request.query_params.get("name") - try: - forms = LeaveForm.objects.get(created_by=pk) - serializer = self.serializer_class(forms, many=False) - except MultipleObjectsReturned: - forms = LeaveForm.objects.filter(created_by=pk) - serializer = self.serializer_class(forms, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - - def put(self, request, *args, **kwargs): - pk = request.query_params.get("id") - receiver = request.data[0] - # send_to = receiver['receiver'] - # receiver_value = User.objects.get(username=send_to) - # receiver_value_designation = HoldsDesignation.objects.filter( - # user=receiver_value) - # lis = list(receiver_value_designation) - # obj = lis[0].designation - form = LeaveForm.objects.get(id=pk) - serializer = self.serializer_class(form, data=request.data[1]) - if serializer.is_valid(): - serializer.save() - forward_file(file_id=receiver['file_id'], receiver=receiver['receiver'], receiver_designation=receiver['receiver_designation'], - remarks=receiver['remarks'], file_extra_JSON=receiver['file_extra_JSON']) - return Response(serializer.data, status=status.HTTP_200_OK) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def delete(self, request, *args, **kwargs): - id = request.query_params.get("id") - is_archived = archive_file(file_id=id) - if (is_archived): - return Response(status=status.HTTP_200_OK) - else: - return Response(status=status.HTTP_400_BAD_REQUEST) - - -class Appraisal(APIView): - serializer_class = Appraisal_serializer - permission_classes = (IsAuthenticated, ) - - def post(self, request): - user_info = request.data[1] - print(request.data) - serializer = self.serializer_class(data=request.data[0]) - if serializer.is_valid(): - serializer.save() - fileId = create_file(uploader=user_info['uploader_name'], uploader_designation=user_info['uploader_designation'], receiver=user_info['receiver_name'], - receiver_designation=user_info['receiver_designation'], src_module="HR", src_object_id=str(serializer.data['id']), file_extra_JSON={"type": "Appraisal"}, attached_file=None) - # forwarded = forward_file(file_id=fileId, receiver=user_info['receiver_name'], receiver_designation=user_info['receiver_designation'], - # remarks="Forwarded to Receiver Inbox", file_extra_JSON={"type": "Appraisal"}) - return Response(serializer.data, status=status.HTTP_200_OK) - else: - print(serializer.errors) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def get(self, request, *args, **kwargs): - pk = request.query_params.get("name") - try: - forms = Appraisalform.objects.get(created_by=pk) - serializer = self.serializer_class(forms, many=False) - except MultipleObjectsReturned: - forms = Appraisalform.objects.filter(created_by=pk) - serializer = self.serializer_class(forms, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - - def put(self, request, *args, **kwargs): - pk = request.query_params.get("id") - print(request.data) - form = Appraisalform.objects.get(id=pk) - receiver = request.data[0] - send_to = receiver['receiver_name'] - receiver_value = User.objects.get(username=send_to) - receiver_value_designation = HoldsDesignation.objects.filter( - user=receiver_value) - lis = list(receiver_value_designation) - obj = lis[0].designation - serializer = self.serializer_class(form, data=request.data[1]) - if serializer.is_valid(): - forward_file(file_id=receiver['file_id'], receiver=send_to, receiver_designation=receiver['receiver_designation'], - remarks=receiver['remarks'], file_extra_JSON=receiver['file_extra_JSON']) - serializer.save() - return Response(serializer.data, status=status.HTTP_200_OK) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def delete(self, request, *args, **kwargs): - id = request.query_params.get("id") - is_archived = archive_file(file_id=id) - if (is_archived): - return Response(status=status.HTTP_200_OK) - else: - return Response(status=status.HTTP_400_BAD_REQUEST) - -# class Forward(APIView): -# def post(self, request, *args, **kwargs): -# forward_file(file_id = request.data['file_id'], receiver = request.data['receiver'], receiver_designation = 'hradmin', remarks = request.data['remarks'], file_extra_JSON = request.data['file_extra_JSON']) -# return Response(status = status.HTTP_200_OK) - - -class GetFormHistory(APIView): - permission_classes = (IsAuthenticated, ) - - def get(self, request, *args, **kwargs): - print(request.query_params) - form_type = request.query_params.get("type") - id = request.query_params.get("id") - person = User.objects.get(username=id) - print(type(person)) - id = person - if form_type == "LTC": - try: - forms = LTCform.objects.get(created_by=id) - serializer = LTC_serializer(forms, many=False) - return Response([serializer.data], status=status.HTTP_200_OK) - except MultipleObjectsReturned: - forms = LTCform.objects.filter(created_by=id) - serializer = LTC_serializer(forms, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - except LTCform.DoesNotExist: - return Response([], status=status.HTTP_200_OK) - elif form_type == "CPDAReimbursement": - try: - forms = CPDAReimbursementform.objects.get(created_by=id) - serializer = CPDAReimbursement_serializer(forms, many=False) - return Response([serializer.data], status=status.HTTP_200_OK) - except MultipleObjectsReturned: - forms = CPDAReimbursementform.objects.filter(created_by=id) - serializer = CPDAReimbursement_serializer(forms, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - except CPDAReimbursementform.DoesNotExist: - return Response([], status=status.HTTP_200_OK) - elif form_type == "CPDAAdvance": - try: - forms = CPDAAdvanceform.objects.get(created_by=id) - serializer = CPDAAdvance_serializer(forms, many=False) - return Response([serializer.data], status=status.HTTP_200_OK) - except MultipleObjectsReturned: - forms = CPDAAdvanceform.objects.filter(created_by=id) - serializer = CPDAAdvance_serializer(forms, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - except CPDAAdvanceform.DoesNotExist: - return Response([], status=status.HTTP_200_OK) - elif form_type == "Appraisal": - try: - forms = Appraisalform.objects.get(created_by=id) - serializer = Appraisal_serializer(forms, many=False) - return Response([serializer.data], status=status.HTTP_200_OK) - except MultipleObjectsReturned: - forms = Appraisalform.objects.filter(created_by=id) - serializer = Appraisal_serializer(forms, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - except Appraisalform.DoesNotExist: - return Response([], status=status.HTTP_200_OK) - elif form_type == "Leave": - try: - forms = LeaveForm.objects.get(created_by=id) - serializer = Leave_serializer(forms, many=False) - return Response([serializer.data], status=status.HTTP_200_OK) - except MultipleObjectsReturned: - forms = LeaveForm.objects.filter(created_by=id) - serializer = Leave_serializer(forms, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - except LeaveForm.DoesNotExist: - return Response([], status=status.HTTP_200_OK) - - -class TrackProgress(APIView): - permission_classes = (IsAuthenticated, ) - - def get(self, request, *args, **kwargs): - file_id = request.query_params.get("id") - progress = view_history(file_id) - return Response({"status": progress}, status=status.HTTP_200_OK) - - -class FormFetch(APIView): - permission_classes = (IsAuthenticated, ) - - def get(self, request, *args, **kwargs): - fileId = request.query_params.get("file_id") - print(fileId) - form_id = request.query_params.get("id") - form_type = request.query_params.get("type") - if form_type == "LTC": - forms = LTCform.objects.get(id=form_id) - serializer = LTC_serializer(forms, many=False) - form = serializer.data - user = User.objects.get(id=int(form["created_by"])) - owner = Tracking.objects.filter(file_id=fileId) - current_owner = owner.last() - current_owner = current_owner.receiver_id - current_owner = current_owner.username - elif form_type == "CPDAReimbursement": - forms = CPDAReimbursementform.objects.get(id=form_id) - serializer = CPDAReimbursement_serializer(forms, many=False) - form = serializer.data - user = User.objects.get(id=int(form["created_by"])) - owner = Tracking.objects.filter(file_id=fileId) - current_owner = owner.last() - current_owner = current_owner.receiver_id - current_owner = current_owner.username - elif form_type == "CPDAAdvance": - forms = CPDAAdvanceform.objects.get(id=form_id) - serializer = CPDAAdvance_serializer(forms, many=False) - form = serializer.data - user = User.objects.get(id=int(form["created_by"])) - owner = Tracking.objects.filter(file_id=fileId) - current_owner = owner.last() - current_owner = current_owner.receiver_id - current_owner = current_owner.username - elif form_type == "Appraisal": - forms = Appraisalform.objects.get(id=form_id) - serializer = Appraisal_serializer(forms, many=False) - form = serializer.data - user = User.objects.get(id=int(form["created_by"])) - owner = Tracking.objects.filter(file_id=fileId) - current_owner = owner.last() - current_owner = current_owner.receiver_id - current_owner = current_owner.username - elif form_type == "Leave": - forms = LeaveForm.objects.get(id=form_id) - serializer = Leave_serializer(forms, many=False) - form = serializer.data - user = User.objects.get(id=int(form["created_by"])) - owner = Tracking.objects.filter(file_id=fileId) - current_owner = owner.last() - current_owner = current_owner.receiver_id - current_owner = current_owner.username - return Response({"form": serializer.data, "creator": user.username, "current_owner": current_owner}, status=status.HTTP_200_OK) - - -class CheckLeaveBalance(APIView): - permission_classes = (IsAuthenticated, ) - serializer_class = LeaveBalanace_serializer - - def get(self, request, *args, **kwargs): - name = request.query_params.get("name") - person = User.objects.get(username=name) - extrainfo = ExtraInfo.objects.get(user=person) - leave_balance = LeaveBalance.objects.get(employeeId=extrainfo) - serializer = self.serializer_class(leave_balance, many=False) - return Response(serializer.data, status=status.HTTP_200_OK) - # return Response([], status=status.HTTP_200_OK) - - def put(self, request, *args, **kwargs): - name = request.query_params.get("name") - # print(request.data) - person = User.objects.get(username=name) - extrainfo = ExtraInfo.objects.get(user=person) - leave_balance = LeaveBalance.objects.get(employeeId=extrainfo) - data1 = request.data - data1['employeeId'] = extrainfo.id - serializer = self.serializer_class(leave_balance, data=data1) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status=status.HTTP_200_OK) - else: - print(serializer.error_messages) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - -class DropDown(APIView): - permission_classes = (IsAuthenticated, ) - - def get(self, request, *args, **kwargs): - user_id = request.query_params.get("username") - user = User.objects.get(username=user_id) - designations = HoldsDesignation.objects.filter(user=user.id) - designation_list = [] - - for design in designations: - design = design.designation - design = design.name - designation_list.append(design) - # print(designation_list) - return Response(designation_list, status=status.HTTP_200_OK) - - -class UserById(APIView): - permission_classes = (IsAuthenticated, ) - - def get(self, request, *args, **kwargs): - user_id = request.query_params.get("id") - user = User.objects.get(id=user_id) - return Response({"username": user.username}, status=status.HTTP_200_OK) - - -class ViewArchived(APIView): - permission_classes = (IsAuthenticated, ) - - def get(self, request, *args, **kwargs): - user_name = request.query_params.get("username") - user_designation = request.query_params.get("designation") - archived_inbox = view_archived( - username=user_name, designation=user_designation, src_module="HR") - return Response(archived_inbox, status=status.HTTP_200_OK) - - -class GetOutbox(APIView): - permission_classes = (IsAuthenticated, ) - - def get(self, request, *args, **kwargs): - name = request.query_params.get("username") - user_designation = request.query_params.get("designation") - outbox = view_outbox( - username=name, designation=user_designation, src_module="HR") - return Response(outbox, status=status.HTTP_200_OK) diff --git a/FusionIIIT/applications/hr2/api/serializers.py b/FusionIIIT/applications/hr2/api/serializers.py index 63efc27cc..b1c05a608 100644 --- a/FusionIIIT/applications/hr2/api/serializers.py +++ b/FusionIIIT/applications/hr2/api/serializers.py @@ -1,65 +1,357 @@ from rest_framework import serializers -from applications.hr2.models import LTCform, CPDAAdvanceform, CPDAReimbursementform, LeaveForm, Appraisalform, LeaveBalance +from django.utils import timezone +from decimal import Decimal +from applications.hr2 import selectors as hr2_selectors +from ..models import ( + Employee, ServiceHistory, LeaveType, EmployeeLeaveBalance, LeaveApplicationNew, + AppraisalPeriod, PerformanceAppraisalNew, TrainingProgram, TrainingNomination, + PromotionApplication, EmployeeAttendance, FacultyWorkload, + EducationalQualification, ProfessionalQualification, PreviousExperience +) +class EmployeeDetailsSerializer(serializers.ModelSerializer): + class Meta: + model = Employee + fields = '__all__' + read_only_fields = ['id'] + +class LeaveApplicationSerializer(serializers.ModelSerializer): + employee_id = serializers.CharField(write_only=True, required=False) + nominee_employee_id = serializers.CharField(write_only=True, required=False, allow_blank=True) + nominee_employee_name = serializers.SerializerMethodField(read_only=True) + is_owner = serializers.SerializerMethodField(read_only=True) -class LTC_serializer(serializers.ModelSerializer): class Meta: - model = LTCform + model = LeaveApplicationNew fields = '__all__' + read_only_fields = [ + 'id', + 'applied_date', + 'approval_status', + 'cancel_status', + 'cancel_requested_at', + 'cancel_decided_at', + 'cancel_requested_by_role', + 'cancel_current_approver_role', + 'cancel_reason', + 'cancel_decision_remarks', + 'extension_status', + 'extension_requested_at', + 'extension_decided_at', + 'extension_requested_by_role', + 'extension_current_approver_role', + 'extension_reason', + 'extension_new_end_date', + 'extension_new_total_days', + 'extension_decision_remarks', + 'resumption_status', + 'resumption_date', + 'resumption_reason', + 'resumption_submitted_at', + 'resumption_decided_at', + 'resumption_current_approver_role', + 'resumption_decision_remarks', + ] + extra_kwargs = { + 'employee': {'required': False}, + } def create(self, validated_data): - return LTCform.objects.create(**validated_data) + # Remove serializer-only fields before model create. + validated_data.pop('nominee_employee_id', None) + validated_data.pop('employee_id', None) + return super().create(validated_data) + + def validate(self, data): + leave_type_name = data.get('leave_type') + if not leave_type_name and self.instance is not None: + leave_type_name = self.instance.leave_type + station_leave_value = None + if 'station_leave' in data: + station_leave_value = data.get('station_leave') + elif self.instance is not None: + station_leave_value = self.instance.station_leave + start_date = data.get('start_date') + end_date = data.get('end_date') + is_half_day = data.get('is_half_day', False) + half_day_slot = data.get('half_day_slot') + if start_date: + today = timezone.now().date() + if start_date < today: + raise serializers.ValidationError({'start_date': 'Start date cannot be in the past.'}) + if start_date and end_date and start_date > end_date: + raise serializers.ValidationError("Start date must be before or equal to end date.") + if start_date and end_date and data.get('total_days') is not None: + if is_half_day: + expected_days = Decimal('0.5') + else: + expected_days = Decimal((end_date - start_date).days + 1) + try: + provided_days = Decimal(str(data.get('total_days'))) + except (TypeError, ValueError): + raise serializers.ValidationError({'total_days': 'Total days must be a valid number.'}) + if provided_days != expected_days: + raise serializers.ValidationError({'total_days': f'Total days should be {expected_days} based on selected dates.'}) + if is_half_day: + if leave_type_name != 'Casual': + raise serializers.ValidationError({'is_half_day': 'Half-day is only allowed for Casual leave.'}) + if not half_day_slot: + raise serializers.ValidationError({'half_day_slot': 'Select AM or PM for half-day leave.'}) + if start_date and end_date and start_date != end_date: + raise serializers.ValidationError({'end_date': 'Half-day leave must be for a single day.'}) + else: + if half_day_slot: + raise serializers.ValidationError({'half_day_slot': 'Half-day slot is only for half-day leave.'}) + if start_date and end_date: + employee = None + request = self.context.get('request') if hasattr(self, 'context') else None + if request and hasattr(request, 'user'): + employee = hr2_selectors.get_employee_for_user(request.user) + if employee is None: + employee_id = data.get('employee_id') + if employee_id: + employee = hr2_selectors.get_employee_by_id_optional(employee_id) + if employee is not None: + overlapping = hr2_selectors.has_overlapping_leave( + employee=employee, + start_date=start_date, + end_date=end_date, + exclude_id=self.instance.id if self.instance is not None else None, + ) + if self.instance is not None: + overlapping = overlapping.exclude(id=self.instance.id) + if overlapping.exists(): + overlap_found = False + for existing in overlapping: + if not is_half_day or not existing.is_half_day: + overlap_found = True + break + if start_date != end_date: + overlap_found = True + break + if existing.start_date != existing.end_date: + overlap_found = True + break + if existing.start_date != start_date: + continue + if existing.half_day_slot == half_day_slot: + overlap_found = True + break + if overlap_found: + raise serializers.ValidationError({'start_date': 'Leave dates overlap with an existing leave request.'}) + leave_type_name = data.get('leave_type') + if leave_type_name and data.get('total_days') is not None: + leave_type = hr2_selectors.get_leave_type_by_name(leave_type_name) + if leave_type: + year = start_date.year + balance = hr2_selectors.get_leave_balance_for_employee_year( + employee=employee, + leave_type=leave_type, + year=year, + ) + if balance is None: + balance = hr2_selectors.get_latest_leave_balance_for_employee( + employee=employee, + leave_type=leave_type, + ) + if balance is None: + raise serializers.ValidationError({'leave_type': 'Leave balance not found for this leave type.'}) + if Decimal(str(data.get('total_days'))) > (balance.current_balance or 0): + raise serializers.ValidationError({'total_days': 'Requested days exceed remaining leave balance.'}) + nominee_id = (data.get('nominee_employee_id') or '').strip() + if nominee_id: + nominee = hr2_selectors.get_employee_by_id_optional(nominee_id) + if not nominee: + raise serializers.ValidationError({'nominee_employee_id': 'Employee not found.'}) + if employee is not None and str(employee.id) == nominee_id: + raise serializers.ValidationError({'nominee_employee_id': 'Nominee must be different from the applicant.'}) + if start_date and end_date: + nominee_overlapping = hr2_selectors.has_overlapping_leave( + employee=nominee, + start_date=start_date, + end_date=end_date, + ).exists() + if nominee_overlapping: + raise serializers.ValidationError({'nominee_employee_id': 'Nominee has overlapping pending or approved leave.'}) + is_cl_rh_leave = leave_type_name in ['Casual', 'Restricted'] + is_station_only = is_cl_rh_leave and station_leave_value == 'NOT_REQUIRED' + is_vacation_leave = leave_type_name == 'Vacation' + if is_cl_rh_leave and not station_leave_value: + raise serializers.ValidationError({'station_leave': 'Select a station leave option for this leave type.'}) + if self.instance is None and not is_station_only and not is_vacation_leave and not nominee_id: + raise serializers.ValidationError({'nominee_employee_id': 'Nominee Employee ID is required for this leave type.'}) + return data + + def get_nominee_employee_name(self, obj): + if not obj.handover_to: + return '' + nominee = hr2_selectors.get_employee_by_id_optional(obj.handover_to) + if not nominee: + return '' + return nominee.user.get_full_name() or nominee.user.username + def get_is_owner(self, obj): + request = self.context.get('request') if hasattr(self, 'context') else None + if not request or not hasattr(request, 'user'): + return False + try: + return obj.employee == request.user.extrainfo + except ExtraInfo.DoesNotExist: + return False + +class LeaveBalanceSerializer(serializers.ModelSerializer): + leave_type_name = serializers.CharField(source='leave_type.name', read_only=True) -class CPDAAdvance_serializer(serializers.ModelSerializer): class Meta: - model = CPDAAdvanceform + model = EmployeeLeaveBalance fields = '__all__' - def create(self, validated_data): - return CPDAAdvanceform.objects.create(**validated_data) +class PerformanceAppraisalSerializer(serializers.ModelSerializer): + class Meta: + model = PerformanceAppraisalNew + fields = '__all__' +class TrainingProgramSerializer(serializers.ModelSerializer): + class Meta: + model = TrainingProgram + fields = '__all__' -class Appraisal_serializer(serializers.ModelSerializer): +class TrainingNominationSerializer(serializers.ModelSerializer): class Meta: - model = Appraisalform + model = TrainingNomination fields = '__all__' - def create(self, validated_data): - return Appraisalform.objects.create(**validated_data) +class PromotionApplicationSerializer(serializers.ModelSerializer): + class Meta: + model = PromotionApplication + fields = '__all__' +class EmployeeAttendanceSerializer(serializers.ModelSerializer): + class Meta: + model = EmployeeAttendance + fields = '__all__' -class CPDAReimbursement_serializer(serializers.ModelSerializer): +class FacultyWorkloadSerializer(serializers.ModelSerializer): class Meta: - model = CPDAReimbursementform + model = FacultyWorkload fields = '__all__' - def create(self, validated_data): - return CPDAReimbursementform.objects.create(**validated_data) +class AppraisalPeriodSerializer(serializers.ModelSerializer): + class Meta: + model = AppraisalPeriod + fields = '__all__' +from ..models import LTCApplicationNew, CPDAAdvanceNew, CPDAReimbursementNew, AppraisalFormNew + +class LTCApplicationSerializer(serializers.ModelSerializer): + employee_id = serializers.CharField(write_only=True, required=False) -class Leave_serializer(serializers.ModelSerializer): class Meta: - model = LeaveForm + model = LTCApplicationNew fields = '__all__' + read_only_fields = ['id', 'applied_date', 'approval_status'] + extra_kwargs = { + 'employee': {'required': False}, + } - def create(self, validated_data): - return LeaveForm.objects.create(**validated_data) + def validate(self, data): + travel_start_date = data.get('travel_start_date') + travel_end_date = data.get('travel_end_date') + if travel_start_date and travel_end_date and travel_start_date > travel_end_date: + raise serializers.ValidationError({'travel_end_date': 'Travel end date must be on or after start date.'}) + previous_ltc_used = data.get('previous_ltc_used') + last_ltc_date = data.get('last_ltc_date') + if previous_ltc_used and not last_ltc_date: + raise serializers.ValidationError({'last_ltc_date': 'Last LTC date is required when previous LTC was used.'}) + + numeric_fields = ['ticket_cost', 'accommodation_cost', 'other_expenses', 'total_amount_claimed'] + for field in numeric_fields: + value = data.get(field) + if value is not None and value < 0: + raise serializers.ValidationError({field: 'Amount must be a non-negative number.'}) + return data + +class CPDAAdvanceSerializer(serializers.ModelSerializer): + employee_id = serializers.CharField(write_only=True, required=False) -class LeaveBalanace_serializer(serializers.ModelSerializer): class Meta: - model = LeaveBalance + model = CPDAAdvanceNew fields = '__all__' + read_only_fields = ['id', 'applied_date', 'approval_status'] + extra_kwargs = { + 'employee': {'required': False}, + } - def create(self, validated_data): - return LeaveBalance.objects.create(**validated_data) + def validate(self, data): + start_date = data.get('start_date') + end_date = data.get('end_date') + if start_date and end_date and start_date > end_date: + raise serializers.ValidationError({'end_date': 'End date must be on or after start date.'}) + + numeric_fields = ['registration_fee', 'travel_expense', 'accommodation_expense', 'other_expenses', 'total_amount'] + for field in numeric_fields: + value = data.get(field) + if value is not None and value < 0: + raise serializers.ValidationError({field: 'Amount must be a non-negative number.'}) + return data +class CPDAReimbursementSerializer(serializers.ModelSerializer): + employee_id = serializers.CharField(write_only=True, required=False) -# class Deignations(serializers.ModelSerializer): -# class Meta: -# model = Deignations -# fields = '__all__' + class Meta: + model = CPDAReimbursementNew + fields = '__all__' + read_only_fields = ['id', 'applied_date', 'approval_status'] + + def validate(self, data): + start_date = data.get('start_date') + end_date = data.get('end_date') + if start_date and end_date and start_date > end_date: + raise serializers.ValidationError({'end_date': 'End date must be on or after start date.'}) + + numeric_fields = ['registration_fee', 'travel_expense', 'accommodation_expense', 'other_expenses', 'total_amount'] + for field in numeric_fields: + value = data.get(field) + if value is not None and value < 0: + raise serializers.ValidationError({field: 'Amount must be a non-negative number.'}) + return data + +class AppraisalFormSerializer(serializers.ModelSerializer): + employee_id = serializers.CharField(write_only=True, required=False) + + class Meta: + model = AppraisalFormNew + fields = '__all__' + read_only_fields = [ + 'id', + 'status', + 'submitted_at', + 'assigned_reviewer_role', + 'assigned_reviewer', + 'assigned_by', + 'assigned_at', + ] + extra_kwargs = { + 'employee': {'required': False}, + } -# def create(self,validated_data): -# return + def validate(self, data): + required_fields = [ + 'employee_name', + 'department', + 'designation', + 'appraisal_year', + 'self_summary', + 'key_responsibilities', + 'achievements', + 'goals_achieved', + 'future_goals', + ] + errors = {} + for field in required_fields: + if not str(data.get(field, '')).strip(): + errors[field] = 'This field is required.' + if errors: + raise serializers.ValidationError(errors) + return data \ No newline at end of file diff --git a/FusionIIIT/applications/hr2/api/urls.py b/FusionIIIT/applications/hr2/api/urls.py index 835434b76..78103c6a0 100644 --- a/FusionIIIT/applications/hr2/api/urls.py +++ b/FusionIIIT/applications/hr2/api/urls.py @@ -1,42 +1,70 @@ -from django.conf.urls import url from django.urls import path -# from . import views -from . import form_views +from . import views - -app_name = 'hr2' +app_name = 'hr_api' urlpatterns = [ - # LTC form - url('ltc/', form_views.LTC.as_view(), name='LTC_form'), - # cpda advance form - url('cpdaadv/', form_views.CPDAAdvance.as_view(), name='CPDAAdvance_form'), - # appraisal form - url('appraisal/', form_views.Appraisal.as_view(), name='Appraisal_form'), - # cpda reimbursement form - url('cpdareim/', form_views.CPDAReimbursement.as_view(), - name='CPDAReimbursement_form'), - # leave form - url('leave/', form_views.Leave.as_view(), name='Leave_form'), - url('formManagement/', form_views.FormManagement.as_view(), name='formManagement'), - url('tracking/', form_views.TrackProgress.as_view(), name='tracking'), - url('formFetch/', form_views.FormFetch.as_view(), name='fetch_form'), - # create for GetForms - url('getForms/', form_views.GetFormHistory.as_view(), name='getForms'), - url('leaveBalance/', form_views.CheckLeaveBalance.as_view(), name='leaveBalance'), - url('getDesignations/', form_views.DropDown.as_view(), name="designations"), - url('getOutbox/', form_views.GetOutbox.as_view(), name='outbox'), - url('getArchive/', form_views.ViewArchived.as_view(), name='archive'), - url('getuserbyid/', form_views.UserById.as_view(), name='userById'), - # url(r'^$', views.service_book, name='hr2'), - # url(r'^hradmin/$', views.hr_admin, name='hradmin'), - # url(r'^edit/(?P\d+)/$', views.edit_employee_details, - # name='editEmployeeDetails'), - # url(r'^viewdetails/(?P\d+)/$', - # views.view_employee_details, name='viewEmployeeDetails'), - # url(r'^editServiceBook/(?P\d+)/$', - # views.edit_employee_servicebook, name='editServiceBook'), - # url(r'^administrativeProfile/$', views.administrative_profile, - # name='administrativeProfile'), - # url(r'^addnew/$', views.add_new_user, name='addnew'), -] + # Employee + path('employees/', views.EmployeeListView.as_view(), name='employee-list'), + path('employees//', views.EmployeeDetailView.as_view(), name='employee-detail'), + + # Leave + path('leave-applications/', views.LeaveApplicationListCreateView.as_view(), name='leave-list-create'), + path('leave-applications//', views.LeaveApplicationDetailView.as_view(), name='leave-detail'), + path('leave-balance/', views.LeaveBalanceView.as_view(), name='leave-balance'), + path('leave-balance//', views.LeaveBalanceView.as_view(), name='leave-balance-other'), + path('leave-applications//responsibility//', views.LeaveResponsibilityView.as_view(), name='leave-responsibility'), + path('leave-applications//request-document/', views.LeaveDocumentRequestView.as_view(), name='leave-request-document'), + path('leave-applications//submit-document/', views.LeaveDocumentSubmitView.as_view(), name='leave-submit-document'), + path('leave-applications//download/', views.LeaveApplicationDownloadView.as_view(), name='leave-download'), + path('leave-applications//withdraw/', views.LeaveApplicationWithdrawView.as_view(), name='leave-withdraw'), + path('leave-applications//cancel-request/', views.LeaveApplicationCancelRequestView.as_view(), name='leave-cancel-request'), + path('leave-applications//cancel-decision//', views.LeaveApplicationCancelDecisionView.as_view(), name='leave-cancel-decision'), + path('leave-applications//extension-request/', views.LeaveApplicationExtensionRequestView.as_view(), name='leave-extension-request'), + path('leave-applications//extension-decision//', views.LeaveApplicationExtensionDecisionView.as_view(), name='leave-extension-decision'), + path('leave-applications//resumption/', views.LeaveResumptionSubmitView.as_view(), name='leave-resumption'), + path('leave-applications//resumption-decision//', views.LeaveResumptionDecisionView.as_view(), name='leave-resumption-decision'), + path('leave-applications///', views.LeaveApproveRejectView.as_view(), name='leave-decision'), + path('leave-nominee/', views.LeaveNomineeDashboardView.as_view(), name='leave-nominee-dashboard'), + path('leave-nominee//', views.LeaveNomineeDecisionView.as_view(), name='leave-nominee-decision'), + + # Attendance + path('attendance/', views.AttendanceView.as_view(), name='attendance'), + + # Appraisal + path('appraisal-periods/', views.AppraisalPeriodListView.as_view(), name='appraisal-periods'), + path('appraisals/', views.AppraisalListView.as_view(), name='appraisals'), + + # Training + path('training-programs/', views.TrainingProgramListView.as_view(), name='training-programs'), + path('training-nominations/', views.TrainingNominationView.as_view(), name='training-nominations'), + + # Promotion + path('promotions/', views.PromotionApplicationView.as_view(), name='promotions'), + + # Faculty Workload + path('workload/', views.FacultyWorkloadView.as_view(), name='workload'), + # Add these to the urlpatterns list + + path('ltc/', views.LTCApplicationListCreateView.as_view(), name='ltc-list-create'), + path('ltc//', views.LTCApplicationDetailView.as_view(), name='ltc-detail'), + path('ltc//download/', views.LTCApplicationDownloadView.as_view(), name='ltc-download'), + path('ltc//withdraw/', views.LTCApplicationWithdrawView.as_view(), name='ltc-withdraw'), + path('ltc///', views.LTCApproveRejectView.as_view(), name='ltc-decision'), + + path('cpda-advances/', views.CPDAAdvanceListCreateView.as_view(), name='cpda-advance-list'), + path('cpda-advances//', views.CPDAAdvanceDetailView.as_view(), name='cpda-advance-detail'), + path('cpda-advances//download/', views.CPDAAdvanceDownloadView.as_view(), name='cpda-advance-download'), + path('cpda-advances//withdraw/', views.CPDAAdvanceWithdrawView.as_view(), name='cpda-advance-withdraw'), + path('cpda-advances///', views.CPDAAdvanceApproveRejectView.as_view(), name='cpda-advance-decision'), + + path('cpda-reimbursements/', views.CPDAReimbursementListCreateView.as_view(), name='cpda-reimbursement-list'), + path('cpda-reimbursements//', views.CPDAReimbursementDetailView.as_view(), name='cpda-reimbursement-detail'), + path('cpda-reimbursements///', views.CPDAReimbursementApproveRejectView.as_view(), name='cpda-reimbursement-decision'), + + path('appraisal-forms/', views.AppraisalFormListCreateView.as_view(), name='appraisal-form-list'), + path('appraisal-forms//', views.AppraisalFormDetailView.as_view(), name='appraisal-form-detail'), + path('appraisal-forms//download/', views.AppraisalFormDownloadView.as_view(), name='appraisal-form-download'), + path('appraisal-forms//review/', views.AppraisalReviewView.as_view(), name='appraisal-form-review'), + path('appraisal-forms//assign/', views.AppraisalAssignView.as_view(), name='appraisal-form-assign'), +] \ No newline at end of file diff --git a/FusionIIIT/applications/hr2/api/views.py b/FusionIIIT/applications/hr2/api/views.py new file mode 100644 index 000000000..515029049 --- /dev/null +++ b/FusionIIIT/applications/hr2/api/views.py @@ -0,0 +1,1533 @@ +import datetime + +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from rest_framework.permissions import IsAuthenticated +from rest_framework.parsers import MultiPartParser, FormParser, JSONParser +from django.shortcuts import get_object_or_404 +from django.db.models import Q +from django.http import HttpResponse +from django.utils import timezone +from applications.globals.models import ExtraInfo, HoldsDesignation +from decimal import Decimal +from ..models import LeaveApplicationNew, EmployeeLeaveBalance, AppraisalFormNew, LeaveType +from ..services import ( + approve_leave_application, reject_leave_application, + handle_academic_responsibility, handle_administrative_responsibility, + mark_attendance, calculate_faculty_workload, + InsufficientLeaveBalanceError, DuplicateLeaveApplicationError, InvalidWorkflowTransitionError +) +from ..selectors import ( + get_employee_by_id, get_all_employees, get_leave_balance_for_employee, + get_leave_applications, get_pending_responsibility_leaves, + get_attendance_for_employee, get_appraisal_periods, get_appraisals_for_employee, + get_available_training_programs, get_nominations_for_employee, + get_promotion_applications, get_faculty_workload +) +from .serializers import ( + EmployeeDetailsSerializer, LeaveApplicationSerializer, LeaveBalanceSerializer, + PerformanceAppraisalSerializer, TrainingProgramSerializer, TrainingNominationSerializer, + PromotionApplicationSerializer, EmployeeAttendanceSerializer, FacultyWorkloadSerializer, + AppraisalPeriodSerializer +) + +# ==================== EMPLOYEE VIEWS ==================== + +class EmployeeListView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + employee_type = request.query_params.get('type') + department_id = request.query_params.get('department') + employees = get_all_employees(employee_type, department_id) + serializer = EmployeeDetailsSerializer(employees, many=True) + return Response(serializer.data) + +class EmployeeDetailView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, employee_id): + employee = get_employee_by_id(employee_id) + serializer = EmployeeDetailsSerializer(employee) + return Response(serializer.data) + + def put(self, request, employee_id): + employee = get_employee_by_id(employee_id) + serializer = EmployeeDetailsSerializer(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) + +# ==================== LEAVE VIEWS ==================== + +class LeaveApplicationListCreateView(APIView): + permission_classes = [IsAuthenticated] + parser_classes = [JSONParser, MultiPartParser, FormParser] + + def get(self, request): + is_hr_staff = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hr', + ).exists() or ( + request.user.extrainfo.user_type == 'staff' + and request.user.extrainfo.department + and request.user.extrainfo.department.name == 'HR' + ) + is_hod = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hod', + ).exists() + is_director = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='director', + ).exists() + is_registrar = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='registrar', + ).exists() + + if is_hr_staff: + leaves = LeaveApplicationNew.objects.all() + elif is_director: + leaves = LeaveApplicationNew.objects.filter( + Q( + approval_status='FORWARDED', + current_approver_role__iexact='Director', + ) | Q(employee=request.user.extrainfo) | Q( + cancel_status='REQUESTED', + cancel_current_approver_role__iexact='Director', + ) | Q( + extension_status='REQUESTED', + extension_current_approver_role__iexact='Director', + ) + ) + elif is_registrar: + leaves = LeaveApplicationNew.objects.filter( + Q( + approval_status='FORWARDED', + current_approver_role__iexact='Registrar', + ) | Q(employee=request.user.extrainfo) | Q( + cancel_status='REQUESTED', + cancel_current_approver_role__iexact='Registrar', + ) | Q( + extension_status='REQUESTED', + extension_current_approver_role__iexact='Registrar', + ) + ) + elif is_hod: + leaves = LeaveApplicationNew.objects.filter( + department=request.user.extrainfo.department.name + ) + else: + leaves = get_leave_applications(request.user.extrainfo) + serializer = LeaveApplicationSerializer(leaves, many=True, context={'request': request}) + return Response(serializer.data) + + def post(self, request): + serializer = LeaveApplicationSerializer(data=request.data, context={'request': request}) + if serializer.is_valid(): + employee = getattr(request.user, 'extrainfo', None) + if employee is None: + employee_id = request.data.get('employee_id') + if employee_id: + employee = get_employee_by_id(employee_id) + if employee is None: + return Response( + {'error': 'Employee profile not found for this user.'}, + status=status.HTTP_400_BAD_REQUEST, + ) + nominee_id = (request.data.get('nominee_employee_id') or '').strip() + nominee_status = 'PENDING' if nominee_id else 'NOT_REQUIRED' + is_director = HoldsDesignation.objects.filter( + working=employee.user, + designation__name__icontains='director', + ).exists() + is_hod = HoldsDesignation.objects.filter( + working=employee.user, + designation__name__icontains='hod', + ).exists() + is_registrar = HoldsDesignation.objects.filter( + working=employee.user, + designation__name__icontains='registrar', + ).exists() + is_hr_admin = HoldsDesignation.objects.filter( + working=employee.user, + designation__name__iregex=r'hr admin|hr administrator', + ).exists() + is_accountant = HoldsDesignation.objects.filter( + working=employee.user, + designation__name__icontains='accountant', + ).exists() + leave_type_name = (request.data.get('leave_type') or '').strip() + is_cl_rh_leave = leave_type_name in ['Casual', 'Restricted'] + employee_name = employee.user.get_full_name() or employee.user.username + department_name = employee.department.name if employee.department else (request.data.get('department') or '') + designation_name = '' + designation_record = HoldsDesignation.objects.filter(working=employee.user).select_related('designation').first() + if designation_record: + designation_name = designation_record.designation.full_name or designation_record.designation.name + else: + designation_name = request.data.get('designation') or '' + approval_status = 'PENDING' + approver_role = '' + if is_director: + approval_status = 'APPROVED' + approver_role = 'Director' + elif is_registrar: + approval_status = 'FORWARDED' + approver_role = 'Director' + elif is_hod: + if is_cl_rh_leave: + approval_status = 'PENDING' + approver_role = 'HOD' + else: + approval_status = 'FORWARDED' + approver_role = 'Director' + elif is_hr_admin or is_accountant: + approval_status = 'FORWARDED' + approver_role = 'Registrar' + + leave_app = serializer.save( + employee=employee, + employee_name=employee_name, + department=department_name, + designation=designation_name, + handover_to=nominee_id, + nominee_status=nominee_status, + approval_status=approval_status, + current_approver_role=approver_role, + ) + if is_director: + _apply_leave_balance_for_approval(leave_app) + leave_app.save(update_fields=['leave_balance_before', 'leave_balance_after']) + refreshed_serializer = LeaveApplicationSerializer(leave_app, context={'request': request}) + return Response(refreshed_serializer.data, status=status.HTTP_201_CREATED) + # Log validation errors to server console for easier debugging without DevTools. + print("LeaveApplication validation errors:", serializer.errors) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class LeaveApplicationDetailView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, pk): + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + serializer = LeaveApplicationSerializer(leave_app) + return Response(serializer.data) + + def put(self, request, pk): + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + if leave_app.employee != request.user.extrainfo and not request.user.is_staff: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + serializer = LeaveApplicationSerializer(leave_app, 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) + + def delete(self, request, pk): + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + if leave_app.status != 'PENDING': + return Response({'error': 'Cannot delete non-pending application'}, status=status.HTTP_400_BAD_REQUEST) + leave_app.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + +class LeaveApplicationDownloadView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, pk): + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + if leave_app.employee != request.user.extrainfo and not request.user.is_staff: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + + lines = [ + f"Leave Application #{leave_app.id}", + "", + f"Employee: {leave_app.employee_name}", + f"Employee ID: {leave_app.employee.id}", + f"Department: {leave_app.department}", + f"Designation: {leave_app.designation}", + "", + f"Leave Type: {leave_app.leave_type}", + f"Station Leave: {leave_app.station_leave or 'N/A'}", + f"Half-day: {'Yes' if leave_app.is_half_day else 'No'}", + f"Half-day Slot: {leave_app.half_day_slot or 'N/A'}", + f"Start Date: {leave_app.start_date}", + f"End Date: {leave_app.end_date}", + f"Total Days: {leave_app.total_days}", + "", + f"Reason: {leave_app.reason}", + f"Contact During Leave: {leave_app.contact_during_leave}", + f"Address During Leave: {leave_app.address_during_leave}", + "", + f"Nominee Employee ID: {leave_app.handover_to or 'N/A'}", + f"Nominee Status: {leave_app.nominee_status}", + "", + f"Approval Status: {leave_app.approval_status}", + f"Applied Date: {leave_app.applied_date}", + ] + + content = "\n".join(lines) + response = HttpResponse(content, content_type='text/plain; charset=utf-8') + response['Content-Disposition'] = f'attachment; filename="leave-application-{leave_app.id}.txt"' + return response + +class LeaveApplicationWithdrawView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + if leave_app.employee != request.user.extrainfo: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + if leave_app.approval_status not in ['PENDING', 'FORWARDED']: + return Response({'error': 'Only pending or forwarded requests can be withdrawn.'}, status=status.HTTP_400_BAD_REQUEST) + + is_registrar = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='registrar', + ).exists() + is_accountant = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='accountant', + ).exists() + is_hr_admin = HoldsDesignation.objects.filter( + working=request.user, + designation__name__iregex=r'hr admin|hr administrator', + ).exists() + + if is_registrar or is_accountant or is_hr_admin: + leave_app.approval_status = 'REJECTED' + if is_registrar: + leave_app.current_approver_role = 'Registrar' + elif is_accountant: + leave_app.current_approver_role = 'Accountant' + else: + leave_app.current_approver_role = 'HR Admin' + else: + leave_app.approval_status = 'WITHDRAWN' + leave_app.current_approver_role = 'Employee' + leave_app.remarks = (request.data.get('remarks') or '').strip() + leave_app.save(update_fields=['approval_status', 'current_approver_role', 'remarks']) + serializer = LeaveApplicationSerializer(leave_app) + return Response(serializer.data) + +class LeaveApplicationCancelRequestView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + if leave_app.employee != request.user.extrainfo: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + if leave_app.approval_status != 'APPROVED': + return Response({'error': 'Only approved requests can be cancelled.'}, status=status.HTTP_400_BAD_REQUEST) + if leave_app.cancel_status != 'NOT_REQUESTED': + return Response({'error': 'Cancellation already processed or pending.'}, status=status.HTTP_400_BAD_REQUEST) + + today = timezone.now().date() + if today >= leave_app.start_date: + return Response( + {'error': 'Cancellation allowed only up to 1 day prior to start date.'}, + status=status.HTTP_400_BAD_REQUEST, + ) + + is_director = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='director', + ).exists() + is_hod = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hod', + ).exists() + is_registrar = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='registrar', + ).exists() + is_accountant = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='accountant', + ).exists() + is_hr_admin = HoldsDesignation.objects.filter( + working=request.user, + designation__name__iregex=r'hr admin|hr administrator', + ).exists() + + requester_role = 'Employee' + if is_director: + requester_role = 'Director' + elif is_hod: + requester_role = 'HOD' + elif is_registrar: + requester_role = 'Registrar' + elif is_accountant: + requester_role = 'Accountant' + elif is_hr_admin: + requester_role = 'HR Admin' + + cancel_approver_role = 'HOD' + if requester_role in ['HOD', 'Director', 'Registrar']: + cancel_approver_role = 'Director' + elif requester_role in ['Accountant', 'HR Admin']: + cancel_approver_role = 'Registrar' + + leave_app.cancel_status = 'REQUESTED' + leave_app.cancel_requested_at = timezone.now() + leave_app.cancel_requested_by_role = requester_role + leave_app.cancel_current_approver_role = cancel_approver_role + leave_app.cancel_reason = (request.data.get('reason') or '').strip() + leave_app.save(update_fields=[ + 'cancel_status', + 'cancel_requested_at', + 'cancel_requested_by_role', + 'cancel_current_approver_role', + 'cancel_reason', + ]) + serializer = LeaveApplicationSerializer(leave_app) + return Response(serializer.data) + +class LeaveApplicationCancelDecisionView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk, decision): + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + decision = (decision or '').lower() + if decision not in ['approve', 'reject']: + return Response({'error': 'Invalid decision'}, status=status.HTTP_400_BAD_REQUEST) + if leave_app.cancel_status != 'REQUESTED': + return Response({'error': 'No cancellation request pending.'}, status=status.HTTP_400_BAD_REQUEST) + + approver_role = (leave_app.cancel_current_approver_role or '').lower() + if approver_role == 'hod': + allowed = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hod', + ).exists() + elif approver_role == 'director': + allowed = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='director', + ).exists() + elif approver_role == 'registrar': + allowed = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='registrar', + ).exists() + else: + allowed = False + + if not allowed: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + + remarks = (request.data.get('remarks') or '').strip() + leave_app.cancel_decided_at = timezone.now() + leave_app.cancel_decision_remarks = remarks + + if decision == 'approve': + leave_app.cancel_status = 'APPROVED' + leave_app.approval_status = 'CANCELLED' + leave_app.current_approver_role = leave_app.cancel_current_approver_role + _restore_leave_balance_for_cancellation(leave_app) + else: + leave_app.cancel_status = 'REJECTED' + + leave_app.save(update_fields=[ + 'cancel_status', + 'cancel_decided_at', + 'cancel_decision_remarks', + 'approval_status', + 'current_approver_role', + 'leave_balance_before', + 'leave_balance_after', + ]) + serializer = LeaveApplicationSerializer(leave_app) + return Response(serializer.data) + +class LeaveApplicationExtensionRequestView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + if leave_app.employee != request.user.extrainfo: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + if leave_app.approval_status != 'APPROVED': + return Response({'error': 'Only approved requests can be extended.'}, status=status.HTTP_400_BAD_REQUEST) + if leave_app.extension_status != 'NOT_REQUESTED': + return Response({'error': 'Extension already processed or pending.'}, status=status.HTTP_400_BAD_REQUEST) + + today = timezone.now().date() + if today >= leave_app.end_date: + return Response({'error': 'Extension allowed only before the original end date.'}, status=status.HTTP_400_BAD_REQUEST) + + new_end_date_raw = request.data.get('new_end_date') + if not new_end_date_raw: + return Response({'error': 'New end date is required.'}, status=status.HTTP_400_BAD_REQUEST) + try: + new_end_date = datetime.datetime.strptime(new_end_date_raw, '%Y-%m-%d').date() + except ValueError: + return Response({'error': 'New end date must be in YYYY-MM-DD format.'}, status=status.HTTP_400_BAD_REQUEST) + if new_end_date <= leave_app.end_date: + return Response({'error': 'New end date must be after the current end date.'}, status=status.HTTP_400_BAD_REQUEST) + + new_total_days = Decimal((new_end_date - leave_app.start_date).days + 1) + + is_director = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='director', + ).exists() + is_hod = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hod', + ).exists() + is_registrar = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='registrar', + ).exists() + is_accountant = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='accountant', + ).exists() + is_hr_admin = HoldsDesignation.objects.filter( + working=request.user, + designation__name__iregex=r'hr admin|hr administrator', + ).exists() + + requester_role = 'Employee' + if is_director: + requester_role = 'Director' + elif is_hod: + requester_role = 'HOD' + elif is_registrar: + requester_role = 'Registrar' + elif is_accountant: + requester_role = 'Accountant' + elif is_hr_admin: + requester_role = 'HR Admin' + + approver_role = 'HOD' + if requester_role in ['HOD', 'Director', 'Registrar']: + approver_role = 'Director' + elif requester_role in ['Accountant', 'HR Admin']: + approver_role = 'Registrar' + + leave_app.extension_status = 'REQUESTED' + leave_app.extension_requested_at = timezone.now() + leave_app.extension_requested_by_role = requester_role + leave_app.extension_current_approver_role = approver_role + leave_app.extension_reason = (request.data.get('reason') or '').strip() + leave_app.extension_new_end_date = new_end_date + leave_app.extension_new_total_days = new_total_days + leave_app.save(update_fields=[ + 'extension_status', + 'extension_requested_at', + 'extension_requested_by_role', + 'extension_current_approver_role', + 'extension_reason', + 'extension_new_end_date', + 'extension_new_total_days', + ]) + serializer = LeaveApplicationSerializer(leave_app) + return Response(serializer.data) + +class LeaveApplicationExtensionDecisionView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk, decision): + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + decision = (decision or '').lower() + if decision not in ['approve', 'reject']: + return Response({'error': 'Invalid decision'}, status=status.HTTP_400_BAD_REQUEST) + if leave_app.extension_status != 'REQUESTED': + return Response({'error': 'No extension request pending.'}, status=status.HTTP_400_BAD_REQUEST) + + approver_role = (leave_app.extension_current_approver_role or '').lower() + if approver_role == 'hod': + allowed = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hod', + ).exists() + elif approver_role == 'director': + allowed = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='director', + ).exists() + elif approver_role == 'registrar': + allowed = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='registrar', + ).exists() + else: + allowed = False + + if not allowed: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + + remarks = (request.data.get('remarks') or '').strip() + leave_app.extension_decided_at = timezone.now() + leave_app.extension_decision_remarks = remarks + + if decision == 'approve': + if not _apply_leave_balance_for_extension(leave_app): + return Response({'error': 'Insufficient leave balance for extension.'}, status=status.HTTP_400_BAD_REQUEST) + leave_app.extension_status = 'APPROVED' + leave_app.current_approver_role = leave_app.extension_current_approver_role + leave_app.end_date = leave_app.extension_new_end_date + leave_app.total_days = leave_app.extension_new_total_days + else: + leave_app.extension_status = 'REJECTED' + + leave_app.save(update_fields=[ + 'extension_status', + 'extension_decided_at', + 'extension_decision_remarks', + 'current_approver_role', + 'leave_balance_before', + 'leave_balance_after', + 'end_date', + 'total_days', + ]) + serializer = LeaveApplicationSerializer(leave_app) + return Response(serializer.data) + +class LeaveResumptionSubmitView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + if leave_app.employee != request.user.extrainfo: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + if leave_app.approval_status != 'APPROVED': + return Response({'error': 'Resumption allowed only for approved leaves.'}, status=status.HTTP_400_BAD_REQUEST) + if leave_app.resumption_status != 'NOT_REQUESTED': + return Response({'error': 'Resumption already submitted or processed.'}, status=status.HTTP_400_BAD_REQUEST) + + today = timezone.now().date() + resumption_date_raw = (request.data.get('resumption_date') or '').strip() + if resumption_date_raw: + try: + resumption_date = datetime.datetime.strptime(resumption_date_raw, '%Y-%m-%d').date() + except ValueError: + return Response({'error': 'Resumption date must be in YYYY-MM-DD format.'}, status=status.HTTP_400_BAD_REQUEST) + else: + resumption_date = today + + if resumption_date <= leave_app.end_date: + return Response({'error': 'Resumption date must be after the leave end date.'}, status=status.HTTP_400_BAD_REQUEST) + + leave_app.resumption_status = 'SUBMITTED' + leave_app.resumption_date = resumption_date + leave_app.resumption_reason = (request.data.get('reason') or '').strip() + leave_app.resumption_submitted_at = timezone.now() + leave_app.resumption_current_approver_role = 'HOD' + leave_app.save(update_fields=[ + 'resumption_status', + 'resumption_date', + 'resumption_reason', + 'resumption_submitted_at', + 'resumption_current_approver_role', + ]) + serializer = LeaveApplicationSerializer(leave_app) + return Response(serializer.data) + +class LeaveResumptionDecisionView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk, decision): + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + decision = (decision or '').lower() + if decision not in ['approve', 'reject']: + return Response({'error': 'Invalid decision'}, status=status.HTTP_400_BAD_REQUEST) + if leave_app.resumption_status != 'SUBMITTED': + return Response({'error': 'No resumption request pending.'}, status=status.HTTP_400_BAD_REQUEST) + + allowed = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hod', + ).exists() + if not allowed: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + + leave_app.resumption_decided_at = timezone.now() + leave_app.resumption_decision_remarks = (request.data.get('remarks') or '').strip() + if decision == 'approve': + leave_app.resumption_status = 'APPROVED' + leave_app.current_approver_role = 'HOD' + else: + leave_app.resumption_status = 'REJECTED' + + leave_app.save(update_fields=[ + 'resumption_status', + 'resumption_decided_at', + 'resumption_decision_remarks', + 'current_approver_role', + ]) + serializer = LeaveApplicationSerializer(leave_app) + return Response(serializer.data) + +class LeaveBalanceView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, employee_id=None): + if employee_id: + employee = get_object_or_404(ExtraInfo, id=employee_id) + else: + employee = request.user.extrainfo + balances_qs = ( + EmployeeLeaveBalance.objects.filter(employee=employee) + .select_related('leave_type') + .order_by('leave_type_id', '-year', '-id') + ) + # Collect the latest balance per leave type without relying on DISTINCT ON. + balances = [] + seen_leave_types = set() + for balance in balances_qs: + if balance.leave_type_id in seen_leave_types: + continue + seen_leave_types.add(balance.leave_type_id) + balances.append(balance) + serializer = LeaveBalanceSerializer(balances, many=True) + return Response(serializer.data) + +class LeaveNomineeDashboardView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + employee = request.user.extrainfo + leaves = LeaveApplicationNew.objects.filter( + handover_to=employee.id, + nominee_status='PENDING', + ).order_by('-applied_date') + serializer = LeaveApplicationSerializer(leaves, many=True) + return Response(serializer.data) + +class LeaveNomineeDecisionView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + action = (request.data.get('action') or '').lower() + if action not in ['accept', 'decline']: + return Response({'error': 'Invalid action'}, status=status.HTTP_400_BAD_REQUEST) + + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + employee = request.user.extrainfo + if leave_app.handover_to != employee.id: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + + leave_app.nominee_status = 'ACCEPTED' if action == 'accept' else 'DECLINED' + leave_app.nominee_responded_at = datetime.datetime.utcnow() + leave_app.save(update_fields=['nominee_status', 'nominee_responded_at']) + serializer = LeaveApplicationSerializer(leave_app) + return Response(serializer.data) + +class LeaveDocumentRequestView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + message = (request.data.get('message') or '').strip() + if not message: + return Response({'error': 'Document request message is required.'}, status=status.HTTP_400_BAD_REQUEST) + + is_hod = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hod', + ).exists() + if not is_hod: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + if leave_app.document_request_status == 'REQUESTED': + return Response({'error': 'Document already requested.'}, status=status.HTTP_400_BAD_REQUEST) + leave_app.document_request_message = message + leave_app.document_request_status = 'REQUESTED' + leave_app.document_requested_at = datetime.datetime.utcnow() + leave_app.save(update_fields=['document_request_message', 'document_request_status', 'document_requested_at']) + serializer = LeaveApplicationSerializer(leave_app) + return Response(serializer.data) + +class LeaveDocumentSubmitView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + submission = (request.data.get('submission') or '').strip() + if not submission: + return Response({'error': 'Document submission is required.'}, status=status.HTTP_400_BAD_REQUEST) + + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + if leave_app.employee != request.user.extrainfo: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + if leave_app.document_request_status != 'REQUESTED': + return Response({'error': 'No document requested for this leave.'}, status=status.HTTP_400_BAD_REQUEST) + + leave_app.document_submission = submission + leave_app.document_request_status = 'SUBMITTED' + leave_app.document_submitted_at = datetime.datetime.utcnow() + leave_app.save(update_fields=['document_submission', 'document_request_status', 'document_submitted_at']) + serializer = LeaveApplicationSerializer(leave_app) + return Response(serializer.data) + +class LeaveResponsibilityView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk, responsibility_type): + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + action = request.data.get('action') + remarks = request.data.get('remarks', '') + try: + if responsibility_type == 'academic': + leave_app = handle_academic_responsibility(leave_app, request.user.extrainfo, action, remarks) + else: + leave_app = handle_administrative_responsibility(leave_app, request.user.extrainfo, action, remarks) + serializer = LeaveApplicationSerializer(leave_app) + return Response(serializer.data) + except (PermissionError, InvalidWorkflowTransitionError) as e: + return Response({'error': str(e)}, status=status.HTTP_403_FORBIDDEN) + +class LeaveApproveRejectView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk, decision): + leave_app = get_object_or_404(LeaveApplicationNew, pk=pk) + remarks = request.data.get('remarks', '') + decision = (decision or '').lower() + if decision not in ['approve', 'reject', 'forward']: + return Response({'error': 'Invalid decision'}, status=status.HTTP_400_BAD_REQUEST) + + is_registrar = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='registrar', + ).exists() + is_director = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='director', + ).exists() + approver_role = 'HOD' + if is_registrar: + approver_role = 'Registrar' + elif is_director: + approver_role = 'Director' + + leave_type_name = (leave_app.leave_type or '').strip() + is_cl_rh_leave = leave_type_name in ['Casual', 'Restricted'] + if decision == 'approve' and not is_cl_rh_leave and approver_role == 'HOD': + return Response( + {'error': 'Only CL/RH leaves can be approved by HOD. Please forward to Director.'}, + status=status.HTTP_400_BAD_REQUEST, + ) + if decision == 'forward' and is_cl_rh_leave: + decision = 'approve' + + if decision == 'approve': + leave_app.approval_status = 'APPROVED' + leave_app.current_approver_role = approver_role + _apply_leave_balance_for_approval(leave_app) + elif decision == 'forward': + leave_app.approval_status = 'FORWARDED' + leave_app.current_approver_role = 'Director' + else: + leave_app.approval_status = 'REJECTED' + leave_app.current_approver_role = approver_role + + leave_app.remarks = remarks + leave_app.save(update_fields=[ + 'approval_status', + 'remarks', + 'current_approver_role', + 'leave_balance_before', + 'leave_balance_after', + ]) + + serializer = LeaveApplicationSerializer(leave_app) + return Response(serializer.data) + + +def _apply_leave_balance_for_approval(leave_app): + leave_type = LeaveType.objects.filter(name__iexact=leave_app.leave_type).first() + if not leave_type: + return + year = leave_app.start_date.year + balance = EmployeeLeaveBalance.objects.filter( + employee=leave_app.employee, + leave_type=leave_type, + year=year, + ).first() + if balance is None: + balance = EmployeeLeaveBalance.objects.filter( + employee=leave_app.employee, + leave_type=leave_type, + ).order_by('-year').first() + if balance is None or balance.year != year: + balance = EmployeeLeaveBalance.objects.create( + employee=leave_app.employee, + leave_type=leave_type, + year=year, + opening_balance=Decimal('0'), + accrued=Decimal('0'), + availed=Decimal('0'), + current_balance=Decimal('0'), + ) + total_days = Decimal(str(leave_app.total_days or 0)) + before_balance = balance.current_balance + balance.availed = (balance.availed or 0) + total_days + balance.current_balance = (balance.current_balance or 0) - total_days + balance.save(update_fields=['availed', 'current_balance']) + + if leave_app.leave_balance_before is None: + leave_app.leave_balance_before = before_balance + leave_app.leave_balance_after = balance.current_balance + +def _restore_leave_balance_for_cancellation(leave_app): + leave_type = LeaveType.objects.filter(name__iexact=leave_app.leave_type).first() + if not leave_type: + return + year = leave_app.start_date.year + balance = EmployeeLeaveBalance.objects.filter( + employee=leave_app.employee, + leave_type=leave_type, + year=year, + ).first() + if balance is None: + balance = EmployeeLeaveBalance.objects.filter( + employee=leave_app.employee, + leave_type=leave_type, + ).order_by('-year').first() + if balance is None: + return + + total_days = Decimal(str(leave_app.total_days or 0)) + before_balance = balance.current_balance + balance.availed = (balance.availed or 0) - total_days + balance.current_balance = (balance.current_balance or 0) + total_days + balance.save(update_fields=['availed', 'current_balance']) + + if leave_app.leave_balance_before is None: + leave_app.leave_balance_before = before_balance + leave_app.leave_balance_after = balance.current_balance + +def _apply_leave_balance_for_extension(leave_app): + if not leave_app.extension_new_total_days: + return False + delta_days = Decimal(str(leave_app.extension_new_total_days)) - Decimal(str(leave_app.total_days or 0)) + if delta_days <= 0: + return False + + leave_type = LeaveType.objects.filter(name__iexact=leave_app.leave_type).first() + if not leave_type: + return False + year = leave_app.start_date.year + balance = EmployeeLeaveBalance.objects.filter( + employee=leave_app.employee, + leave_type=leave_type, + year=year, + ).first() + if balance is None: + balance = EmployeeLeaveBalance.objects.filter( + employee=leave_app.employee, + leave_type=leave_type, + ).order_by('-year').first() + if balance is None: + return False + + if (balance.current_balance or 0) < delta_days: + return False + + before_balance = balance.current_balance + balance.availed = (balance.availed or 0) + delta_days + balance.current_balance = (balance.current_balance or 0) - delta_days + balance.save(update_fields=['availed', 'current_balance']) + + if leave_app.leave_balance_before is None: + leave_app.leave_balance_before = before_balance + leave_app.leave_balance_after = balance.current_balance + return True + +# ==================== ATTENDANCE VIEWS ==================== + +class AttendanceView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + from_date = request.query_params.get('from_date') + to_date = request.query_params.get('to_date') + attendance = get_attendance_for_employee(request.user.extrainfo, from_date, to_date) + serializer = EmployeeAttendanceSerializer(attendance, many=True) + return Response(serializer.data) + + def post(self, request): + attendance = mark_attendance( + employee_extra_info=request.user.extrainfo, + date=request.data.get('date'), + status=request.data.get('status'), + in_time=request.data.get('in_time'), + out_time=request.data.get('out_time'), + remarks=request.data.get('remarks', '') + ) + serializer = EmployeeAttendanceSerializer(attendance) + return Response(serializer.data, status=status.HTTP_201_CREATED) + +# ==================== APPRAISAL VIEWS ==================== + +class AppraisalPeriodListView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + is_active = request.query_params.get('is_active') + periods = get_appraisal_periods(is_active) + serializer = AppraisalPeriodSerializer(periods, many=True) + return Response(serializer.data) + +class AppraisalListView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + period_id = request.query_params.get('period') + appraisals = get_appraisals_for_employee(request.user.extrainfo, period_id) + serializer = PerformanceAppraisalSerializer(appraisals, many=True) + return Response(serializer.data) + + def post(self, request): + serializer = PerformanceAppraisalSerializer(data=request.data) + if serializer.is_valid(): + serializer.save(employee=request.user.extrainfo) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +# ==================== TRAINING VIEWS ==================== + +class TrainingProgramListView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + programs = get_available_training_programs() + serializer = TrainingProgramSerializer(programs, many=True) + return Response(serializer.data) + +class TrainingNominationView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + nominations = get_nominations_for_employee(request.user.extrainfo) + serializer = TrainingNominationSerializer(nominations, many=True) + return Response(serializer.data) + + def post(self, request): + serializer = TrainingNominationSerializer(data=request.data) + if serializer.is_valid(): + serializer.save(employee=request.user.extrainfo, nominated_by=request.user.extrainfo) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +# ==================== PROMOTION VIEWS ==================== + +class PromotionApplicationView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + applications = get_promotion_applications(request.user.extrainfo) + serializer = PromotionApplicationSerializer(applications, many=True) + return Response(serializer.data) + + def post(self, request): + serializer = PromotionApplicationSerializer(data=request.data) + if serializer.is_valid(): + serializer.save(employee=request.user.extrainfo) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +# ==================== FACULTY WORKLOAD VIEWS ==================== + +class FacultyWorkloadView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + semester = request.query_params.get('semester') + year = request.query_params.get('year') + workloads = get_faculty_workload(request.user.extrainfo, semester, year) + serializer = FacultyWorkloadSerializer(workloads, many=True) + return Response(serializer.data) + + def post(self, request): + workload = calculate_faculty_workload( + request.user.extra_info, + request.data.get('semester'), + request.data.get('year') + ) + serializer = FacultyWorkloadSerializer(workload) + return Response(serializer.data) + +from ..models import LTCApplicationNew, CPDAAdvanceNew, CPDAReimbursementNew, AppraisalFormNew +from ..services import apply_ltc, approve_ltc, reject_ltc, apply_cpda_advance, approve_cpda_advance, reject_cpda_advance, apply_cpda_reimbursement, approve_cpda_reimbursement, reject_cpda_reimbursement, submit_appraisal, review_appraisal +from ..selectors import get_ltc_applications, get_cpda_advances, get_cpda_reimbursements, get_appraisal_forms +from .serializers import LTCApplicationSerializer, CPDAAdvanceSerializer, CPDAReimbursementSerializer, AppraisalFormSerializer + +# ==================== LTC VIEWS ==================== + +class LTCApplicationListCreateView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + is_hr_staff = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hr', + ).exists() or ( + request.user.extrainfo.user_type == 'staff' + and request.user.extrainfo.department + and request.user.extrainfo.department.name == 'HR' + ) + is_accountant = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='accountant', + ).exists() + + if is_hr_staff: + ltcs = LTCApplicationNew.objects.filter(approval_status__in=['PENDING', 'FORWARDED']) + elif is_accountant: + ltcs = LTCApplicationNew.objects.filter( + approval_status='FORWARDED', + accountant_status__iexact='PENDING', + ) + else: + ltcs = get_ltc_applications(request.user.extrainfo) + serializer = LTCApplicationSerializer(ltcs, many=True) + return Response(serializer.data) + + def post(self, request): + serializer = LTCApplicationSerializer(data=request.data) + if serializer.is_valid(): + serializer.save(employee=request.user.extrainfo) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class LTCApplicationDetailView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, pk): + ltc = get_object_or_404(LTCApplicationNew, pk=pk) + serializer = LTCApplicationSerializer(ltc) + return Response(serializer.data) + + def put(self, request, pk): + ltc = get_object_or_404(LTCApplicationNew, pk=pk) + if ltc.employee != request.user.extrainfo: + return Response({'error': 'Not authorized'}, status=403) + serializer = LTCApplicationSerializer(ltc, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=400) + +class LTCApplicationDownloadView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, pk): + ltc = get_object_or_404(LTCApplicationNew, pk=pk) + if ltc.employee != request.user.extrainfo and not request.user.is_staff: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + + lines = [ + f"LTC Application #{ltc.id}", + "", + f"Employee: {ltc.employee_name}", + f"Employee ID: {ltc.employee.id}", + f"Department: {ltc.department}", + f"Designation: {ltc.designation}", + "", + f"Block Year: {ltc.ltc_block_year}", + f"Travel Start: {ltc.travel_start_date}", + f"Travel End: {ltc.travel_end_date}", + f"Destination: {ltc.destination}", + f"Purpose: {ltc.purpose_of_travel}", + "", + f"Approval Status: {ltc.approval_status}", + f"Applied Date: {ltc.applied_date}", + ] + + content = "\n".join(lines) + response = HttpResponse(content, content_type='text/plain; charset=utf-8') + response['Content-Disposition'] = f'attachment; filename="ltc-application-{ltc.id}.txt"' + return response + +class LTCApplicationWithdrawView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + ltc = get_object_or_404(LTCApplicationNew, pk=pk) + if ltc.employee != request.user.extrainfo: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + if ltc.approval_status != 'PENDING': + return Response({'error': 'Only pending requests can be withdrawn.'}, status=status.HTTP_400_BAD_REQUEST) + + ltc.approval_status = 'WITHDRAWN' + ltc.remarks = (request.data.get('remarks') or '').strip() + ltc.save(update_fields=['approval_status', 'remarks']) + serializer = LTCApplicationSerializer(ltc) + return Response(serializer.data) + +class LTCApproveRejectView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk, decision): + ltc = get_object_or_404(LTCApplicationNew, pk=pk) + remarks = request.data.get('remarks', '') + decision = (decision or '').lower() + if decision not in ['approve', 'reject', 'forward']: + return Response({'error': 'Invalid decision'}, status=status.HTTP_400_BAD_REQUEST) + + if decision == 'approve': + ltc.approval_status = 'APPROVED' + ltc.accountant_status = 'APPROVED' + elif decision == 'forward': + ltc.approval_status = 'FORWARDED' + ltc.verified_by_hr = True + ltc.accountant_status = 'PENDING' + else: + ltc.approval_status = 'REJECTED' + ltc.accountant_status = 'REJECTED' + + ltc.remarks = remarks + ltc.save(update_fields=['approval_status', 'remarks', 'verified_by_hr', 'accountant_status']) + + serializer = LTCApplicationSerializer(ltc) + return Response(serializer.data) + +# ==================== CPDA ADVANCE VIEWS ==================== + +class CPDAAdvanceListCreateView(APIView): + permission_classes = [IsAuthenticated] + def get(self, request): + is_hr_staff = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hr', + ).exists() or ( + request.user.extrainfo.user_type == 'staff' + and request.user.extrainfo.department + and request.user.extrainfo.department.name == 'HR' + ) + is_accountant = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='accountant', + ).exists() + is_director = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='director', + ).exists() + + if is_director: + advances = CPDAAdvanceNew.objects.filter( + approval_status='FORWARDED', + accountant_processing_status__iexact='DIRECTOR_REVIEW', + ) + elif is_hr_staff: + advances = CPDAAdvanceNew.objects.filter(approval_status='PENDING') + elif is_accountant: + advances = CPDAAdvanceNew.objects.filter( + approval_status='FORWARDED', + accountant_processing_status__in=['PENDING', 'DIRECTOR_APPROVED'], + ) + else: + advances = get_cpda_advances(request.user.extrainfo) + serializer = CPDAAdvanceSerializer(advances, many=True) + return Response(serializer.data) + def post(self, request): + serializer = CPDAAdvanceSerializer(data=request.data) + if serializer.is_valid(): + serializer.save(employee=request.user.extrainfo) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class CPDAAdvanceDetailView(APIView): + permission_classes = [IsAuthenticated] + def get(self, request, pk): + cpda = get_object_or_404(CPDAAdvanceNew, pk=pk) + serializer = CPDAAdvanceSerializer(cpda) + return Response(serializer.data) + +class CPDAAdvanceDownloadView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, pk): + cpda = get_object_or_404(CPDAAdvanceNew, pk=pk) + if cpda.employee != request.user.extrainfo and not request.user.is_staff: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + + lines = [ + f"CPDA Advance #{cpda.id}", + "", + f"Employee: {cpda.employee_name}", + f"Employee ID: {cpda.employee.id}", + f"Department: {cpda.department}", + f"Designation: {cpda.designation}", + "", + f"Event Name: {cpda.event_name}", + f"Event Type: {cpda.event_type}", + f"Start Date: {cpda.start_date}", + f"End Date: {cpda.end_date}", + f"Total Amount: {cpda.total_amount}", + "", + f"Approval Status: {cpda.approval_status}", + f"Applied Date: {cpda.applied_date}", + ] + + content = "\n".join(lines) + response = HttpResponse(content, content_type='text/plain; charset=utf-8') + response['Content-Disposition'] = f'attachment; filename="cpda-advance-{cpda.id}.txt"' + return response + +class CPDAAdvanceWithdrawView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + cpda = get_object_or_404(CPDAAdvanceNew, pk=pk) + if cpda.employee != request.user.extrainfo: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + if cpda.approval_status != 'PENDING': + return Response({'error': 'Only pending requests can be withdrawn.'}, status=status.HTTP_400_BAD_REQUEST) + + cpda.approval_status = 'WITHDRAWN' + cpda.remarks = (request.data.get('remarks') or '').strip() + cpda.save(update_fields=['approval_status', 'remarks']) + serializer = CPDAAdvanceSerializer(cpda) + return Response(serializer.data) + +class CPDAAdvanceApproveRejectView(APIView): + permission_classes = [IsAuthenticated] + def post(self, request, pk, decision): + cpda = get_object_or_404(CPDAAdvanceNew, pk=pk) + remarks = request.data.get('remarks', '') + decision = (decision or '').lower() + if decision not in ['approve', 'reject', 'forward-accountant', 'forward-director']: + return Response({'error': 'Invalid decision'}, status=status.HTTP_400_BAD_REQUEST) + + if decision == 'forward-accountant': + cpda.approval_status = 'FORWARDED' + cpda.verified_by_hr = True + cpda.accountant_processing_status = 'PENDING' + elif decision == 'forward-director': + cpda.approval_status = 'FORWARDED' + cpda.accountant_processing_status = 'DIRECTOR_REVIEW' + elif decision == 'approve': + if cpda.accountant_processing_status == 'DIRECTOR_REVIEW': + cpda.accountant_processing_status = 'DIRECTOR_APPROVED' + cpda.approval_status = 'FORWARDED' + else: + cpda.approval_status = 'APPROVED' + cpda.accountant_processing_status = 'APPROVED' + else: + cpda.approval_status = 'REJECTED' + cpda.accountant_processing_status = 'REJECTED' + cpda.remarks = remarks + cpda.save(update_fields=['approval_status', 'remarks', 'verified_by_hr', 'accountant_processing_status']) + serializer = CPDAAdvanceSerializer(cpda) + return Response(serializer.data) + +# ==================== CPDA REIMBURSEMENT VIEWS ==================== + +class CPDAReimbursementListCreateView(APIView): + permission_classes = [IsAuthenticated] + def get(self, request): + reims = get_cpda_reimbursements(request.user.extrainfo) + serializer = CPDAReimbursementSerializer(reims, many=True) + return Response(serializer.data) + def post(self, request): + serializer = CPDAReimbursementSerializer(data=request.data) + if serializer.is_valid(): + serializer.save(employee=request.user.extrainfo) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class CPDAReimbursementDetailView(APIView): + permission_classes = [IsAuthenticated] + def get(self, request, pk): + reim = get_object_or_404(CPDAReimbursementNew, pk=pk) + serializer = CPDAReimbursementSerializer(reim) + return Response(serializer.data) + +class CPDAReimbursementApproveRejectView(APIView): + permission_classes = [IsAuthenticated] + def post(self, request, pk, decision): + reim = get_object_or_404(CPDAReimbursementNew, pk=pk) + remarks = request.data.get('remarks', '') + if decision == 'approve': + reim = approve_cpda_reimbursement(reim, request.user.extrainfo, remarks) + else: + reim = reject_cpda_reimbursement(reim, request.user.extrainfo, remarks) + serializer = CPDAReimbursementSerializer(reim) + return Response(serializer.data) + +# ==================== APPRAISAL FORM VIEWS ==================== + +class AppraisalFormListCreateView(APIView): + permission_classes = [IsAuthenticated] + def get(self, request): + is_hr_staff = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hr', + ).exists() or ( + request.user.extrainfo.user_type == 'staff' + and request.user.extrainfo.department + and request.user.extrainfo.department.name == 'HR' + ) + is_hod = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hod', + ).exists() + is_director = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='director', + ).exists() + + if is_hr_staff: + appraisals = AppraisalFormNew.objects.all().order_by('-submitted_at') + elif is_director: + appraisals = AppraisalFormNew.objects.filter( + assigned_reviewer_role__iexact='DIRECTOR', + ).filter( + Q(assigned_reviewer__isnull=True) + | Q(assigned_reviewer=request.user.extrainfo) + ).filter( + status__in=['PENDING', 'REVIEWED'] + ).order_by('-submitted_at') + elif is_hod: + appraisals = AppraisalFormNew.objects.filter( + assigned_reviewer_role__iexact='HOD', + department=request.user.extrainfo.department.name, + ).filter( + Q(assigned_reviewer__isnull=True) + | Q(assigned_reviewer=request.user.extrainfo) + ).filter( + status='PENDING' + ).order_by('-submitted_at') + else: + appraisals = get_appraisal_forms(request.user.extrainfo) + serializer = AppraisalFormSerializer(appraisals, many=True) + return Response(serializer.data) + def post(self, request): + serializer = AppraisalFormSerializer(data=request.data) + if serializer.is_valid(): + serializer.save(employee=request.user.extrainfo) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class AppraisalFormDetailView(APIView): + permission_classes = [IsAuthenticated] + def get(self, request, pk): + appraisal = get_object_or_404(AppraisalFormNew, pk=pk) + serializer = AppraisalFormSerializer(appraisal) + return Response(serializer.data) + +class AppraisalFormDownloadView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, pk): + appraisal = get_object_or_404(AppraisalFormNew, pk=pk) + if appraisal.employee != request.user.extrainfo and not request.user.is_staff: + return Response({'error': 'Not authorized'}, status=status.HTTP_403_FORBIDDEN) + + lines = [ + f"Appraisal Form #{appraisal.id}", + "", + f"Employee: {appraisal.employee_name}", + f"Employee ID: {appraisal.employee.id}", + f"Department: {appraisal.department}", + f"Designation: {appraisal.designation}", + "", + f"Appraisal Year: {appraisal.appraisal_year}", + f"Status: {appraisal.status}", + f"Submitted At: {appraisal.submitted_at}", + ] + + content = "\n".join(lines) + response = HttpResponse(content, content_type='text/plain; charset=utf-8') + response['Content-Disposition'] = f'attachment; filename="appraisal-{appraisal.id}.txt"' + return response + +class AppraisalReviewView(APIView): + permission_classes = [IsAuthenticated] + def post(self, request, pk): + appraisal = get_object_or_404(AppraisalFormNew, pk=pk) + action = (request.data.get('action') or 'review').lower() + remarks = request.data.get('remarks', '') + rating = request.data.get('rating', '') + + is_hod = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hod', + ).exists() + is_director = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='director', + ).exists() + if is_hod and appraisal.assigned_reviewer_role.upper() != 'HOD': + return Response({'error': 'Not assigned to HOD review.'}, status=status.HTTP_403_FORBIDDEN) + if is_director and appraisal.assigned_reviewer_role.upper() != 'DIRECTOR': + return Response({'error': 'Not assigned to Director review.'}, status=status.HTTP_403_FORBIDDEN) + if not (is_hod or is_director): + return Response({'error': 'Not authorized to review.'}, status=status.HTTP_403_FORBIDDEN) + + appraisal.reviewer_id = str(request.user.extrainfo.id) + appraisal.reviewer_comments = remarks + if rating: + appraisal.rating = str(rating) + + if action == 'approve': + appraisal.status = 'APPROVED' + appraisal.assigned_reviewer_role = '' + appraisal.assigned_reviewer = None + elif action == 'forward': + appraisal.status = 'REVIEWED' + appraisal.assigned_reviewer_role = 'DIRECTOR' + appraisal.assigned_reviewer = None + else: + appraisal.status = 'REVIEWED' + + appraisal.save(update_fields=[ + 'reviewer_id', + 'reviewer_comments', + 'rating', + 'status', + 'assigned_reviewer_role', + 'assigned_reviewer', + ]) + serializer = AppraisalFormSerializer(appraisal) + return Response(serializer.data) + +class AppraisalAssignView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + appraisal = get_object_or_404(AppraisalFormNew, pk=pk) + is_hr_staff = HoldsDesignation.objects.filter( + working=request.user, + designation__name__icontains='hr', + ).exists() or ( + request.user.extrainfo.user_type == 'staff' + and request.user.extrainfo.department + and request.user.extrainfo.department.name == 'HR' + ) + if not is_hr_staff: + return Response({'error': 'Not authorized to assign.'}, status=status.HTTP_403_FORBIDDEN) + + role = (request.data.get('role') or '').upper() + reviewer_id = (request.data.get('reviewer_id') or '').strip() + if role not in ['HOD', 'DIRECTOR']: + return Response({'error': 'Role must be HOD or DIRECTOR.'}, status=status.HTTP_400_BAD_REQUEST) + if appraisal.status != 'PENDING': + return Response({'error': 'Only pending appraisals can be assigned.'}, status=status.HTTP_400_BAD_REQUEST) + + assigned_reviewer = None + if reviewer_id: + assigned_reviewer = ExtraInfo.objects.filter(id=reviewer_id).first() + if not assigned_reviewer: + return Response({'error': 'Reviewer not found.'}, status=status.HTTP_400_BAD_REQUEST) + + appraisal.assigned_reviewer_role = role + appraisal.assigned_reviewer = assigned_reviewer + appraisal.assigned_by = request.user.extrainfo + appraisal.assigned_at = timezone.now() + appraisal.save(update_fields=[ + 'assigned_reviewer_role', + 'assigned_reviewer', + 'assigned_by', + 'assigned_at', + ]) + serializer = AppraisalFormSerializer(appraisal) + return Response(serializer.data) + diff --git a/FusionIIIT/applications/hr2/apps.py b/FusionIIIT/applications/hr2/apps.py index 002c0de9d..a3848b069 100644 --- a/FusionIIIT/applications/hr2/apps.py +++ b/FusionIIIT/applications/hr2/apps.py @@ -1,5 +1,6 @@ from django.apps import AppConfig - class Hr2Config(AppConfig): name = 'applications.hr2' + label = 'hr2' + # For Django 3.1.5, no default_auto_field needed; it uses AutoField \ No newline at end of file diff --git a/FusionIIIT/applications/hr2/form_views.py b/FusionIIIT/applications/hr2/form_views.py deleted file mode 100644 index 3cc08fb93..000000000 --- a/FusionIIIT/applications/hr2/form_views.py +++ /dev/null @@ -1,323 +0,0 @@ -from .serializers import LTC_serializer, CPDAAdvance_serializer, Appraisal_serializer, CPDAReimbursement_serializer, Leave_serializer -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status -from rest_framework.decorators import permission_classes, api_view -from rest_framework.permissions import IsAuthenticated, AllowAny -from .models import LTCform, CPDAAdvanceform, CPDAReimbursementform, Leaveform, Appraisalform -from django.contrib.auth import get_user_model -from django.core.exceptions import MultipleObjectsReturned -from applications.filetracking.sdk.methods import * -from applications.globals.models import Designation, HoldsDesignation - -class LTC(APIView): - serializer_class = LTC_serializer - permission_classes = (AllowAny, ) - def post(self, request): - if 'Mobile-OS' in request.META: - user_info = request.data[0] - serializer = self.serializer_class(data = request.data[1]) - if serializer.is_valid(): - serializer.save() - file_id = create_file(uploader = user_info['uploader_name'], uploader_designation = user_info['uploader_designation'], receiver = "21BCS140", receiver_designation="hradmin", src_module="HR", src_object_id= str(serializer.data['id']), file_extra_JSON= {"type": "LTC"}, attached_file= None) - return Response(serializer.data, status= status.HTTP_200_OK) - else: - return Response(serializer.errors, status= status.HTTP_400_BAD_REQUEST) - id = request.query_params.get("id") - try: - employee = ExtraInfo.objects.get(user__id=id) - except: - raise Http404("Post does not exist! id doesnt exist") - - print(employee.user_type) - - - if(employee.user_type == 'faculty'): - template = 'hr2Module/ltc_form.html' - - if request.method == "POST": - family_mem_a = request.POST.get('id_family_mem_a', '') - family_mem_b = request.POST.get('id_family_mem_b', '') - family_mem_c = request.POST.get('id_family_mem_c', '') - - - detailsOfFamilyMembers = ', '.join(filter(None, [family_mem_a, family_mem_b, family_mem_c])) - - - request.POST = request.POST.copy() - request.POST['detailsOfFamilyMembersAlreadyDone'] = detailsOfFamilyMembers - - - family_members = [] - for i in range(1, 7): # Loop through input fields for each family member - name = request.POST.get(f'info_{i}_2', '') # Get the name - age = request.POST.get(f'info_{i}_3', '') # Get the age - if name and age: # Check if both name and age are provided - family_members.append(f"{name} ({age} years)") # Concatenate name and age - - family_members_str = ', '.join(family_members) - - # Populate the form with concatenated family member details - request.POST['familyMembersAboutToAvail'] = family_members_str - - dependents = [] - for i in range(1, 7): # Loop through input fields for each dependent - name = request.POST.get(f'd_info_{i}_2', '') # Get the name - age = request.POST.get(f'd_info_{i}_3', '') # Get the age - why_dependent = request.POST.get(f'd_info_{i}_4', '') # Get the reason for dependency - if name and age: # Check if both name and age are provided - dependents.append(f"{name} ({age} years), {why_dependent}") # Concatenate name, age, and reason - - - # Concatenate all dependent strings into a single string - dependents_str = ', '.join(dependents) - - # Populate the form with concatenated dependent details - request.POST['detailsOfDependents'] = dependents_str - - # print("first",request.POST['familyMembersAboutToAvail']) - pf_no = int(request.POST.get('pf_no')) if request.POST.get('pf_no') else None - basicPay = int(request.POST.get('basicPay')) if request.POST.get('basicPay') else None - amountOfAdvanceRequired = int(request.POST.get('amountOfAdvanceRequired')) if request.POST.get('amountOfAdvanceRequired') else None - phoneNumberForContact = int(request.POST.get('phoneNumberForContact')) if request.POST.get('phoneNumberForContact') else None - - - try: - ltc_request = LTCform.objects.create( - employee_id = id, - detailsOfFamilyMembersAlreadyDone=request.POST.get('detailsOfFamilyMembersAlreadyDone', ''), - familyMembersAboutToAvail=request.POST.get('familyMembersAboutToAvail', ''), - detailsOfDependents=request.POST.get('detailsOfDependents', ''), - name=request.POST.get('name', ''), - blockYear=request.POST.get('blockYear', ''), - pf_no=request.POST.get('pf_no', ''), - basicPay=request.POST.get('basicPay', ''), - designation=request.POST.get('designation', ''), - departmentInfo=request.POST.get('departmentInfo', ''), - leaveAvailability=request.POST.get('leaveAvailability', ''), - leaveStartDate=request.POST.get('leaveStartDate', ''), - leaveEndDate=request.POST.get('leaveEndDate', ''), - dateOfLeaveForFamily=request.POST.get('dateOfLeaveForFamily', ''), - natureOfLeave=request.POST.get('natureOfLeave', ''), - purposeOfLeave=request.POST.get('purposeOfLeave', ''), - hometownOrNot=request.POST.get('hometownOrNot', ''), - placeOfVisit=request.POST.get('placeOfVisit', ''), - addressDuringLeave=request.POST.get('addressDuringLeave', ''), - modeForVacation=request.POST.get('modeForVacation', ''), - detailsOfFamilyMembers=request.POST.get('detailsOfFamilyMembers', ''), - amountOfAdvanceRequired=request.POST.get('amountOfAdvanceRequired', ''), - certifiedFamilyDependents=request.POST.get('certifiedFamilyDependents', ''), - certifiedAdvance =request.POST.get('certifiedAdvance ', ''), - adjustedMonth=request.POST.get('adjustedMonth', ''), - date=request.POST.get('date', ''), - phoneNumberForContact=request.POST.get('phoneNumberForContact', '') - ) - print("done") - messages.success(request, "Ltc form filled successfully") - except Exception as e: - print("error" , e) - messages.warning(request, "Fill not correctly") - context = {'employee': employee} - return render(request, template, context) - - - # Query all LTC requests - ltc_requests = LTCform.objects.filter(employee_id=id) - - context = {'employee': employee, 'ltc_requests': ltc_requests} - - return render(request, template, context) - else: - return render(request, 'hr2Module/edit.html') - - - - def get(self, request, *args, **kwargs): - pk = request.query_params.get("name") - print(pk) - try: - forms = LTCform.objects.get(name = pk) - serializer = self.serializer_class(forms, many = False) - except MultipleObjectsReturned: - forms = LTCform.objects.filter(name = pk) - serializer = self.serializer_class(forms, many = True) - return Response(serializer.data, status = status.HTTP_200_OK) - - def put(self, request, *args, **kwargs): - pk = request.query_params.get("id") - print(pk) - form = LTCform.objects.get(id = pk) - print(form) - serializer = self.serializer_class(form, data = request.data) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status = status.HTTP_200_OK) - else: - return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST) - -class FormManagement(APIView): - permission_classes = (AllowAny, ) - def get(self, request, *args, **kwargs): - username = request.query_params.get("username") - designation = request.query_params.get("designation") - inbox = view_inbox(username = username, designation = designation, src_module = "HR") - return Response(inbox, status = status.HTTP_200_OK) - - def post(self, request, *args, **kwargs): - username = request.data['receiver'] - receiver_value = User.objects.get(username=username) - receiver_value_designation= HoldsDesignation.objects.filter(user=receiver_value) - lis = list(receiver_value_designation) - obj=lis[0].designation - forward_file(file_id = request.data['file_id'], receiver = request.data['receiver'], receiver_designation = obj.name, remarks = request.data['remarks'], file_extra_JSON = request.data['file_extra_JSON']) - return Response(status = status.HTTP_200_OK) - - -class CPDAAdvance(APIView): - serializer_class = CPDAAdvance_serializer - permission_classes = (AllowAny, ) - def post(self, request): - user_info = request.data[0] - serializer = self.serializer_class(data = request.data[1]) - if serializer.is_valid(): - serializer.save() - file_id = create_file(uploader = user_info['uploader_name'], uploader_designation = user_info['uploader_designation'], receiver = "vkjain", receiver_designation="CSE HOD", src_module="HR", src_object_id= str(serializer.data['id']), file_extra_JSON= {"type": "CPDAAdvance"}, attached_file= None) - return Response(serializer.data, status= status.HTTP_200_OK) - else: - return Response(serializer.errors, status= status.HTTP_400_BAD_REQUEST) - - def get(self, request, *args, **kwargs): - pk = request.query_params.get("name") - print(pk) - try: - forms = CPDAAdvanceform.objects.get(name = pk) - serializer = self.serializer_class(forms, many = False) - except MultipleObjectsReturned: - forms = CPDAAdvanceform.objects.filter(name = pk) - serializer = self.serializer_class(forms, many = True) - return Response(serializer.data, status = status.HTTP_200_OK) - - def put(self, request, *args, **kwargs): - pk = request.query_params.get("id") - print(pk) - form = CPDAAdvanceform.objects.get(id = pk) - print(form) - serializer = self.serializer_class(form, data = request.data) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status = status.HTTP_200_OK) - else: - return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST) - -class CPDAReimbursement(APIView): - serializer_class = CPDAReimbursement_serializer - permission_classes = (AllowAny, ) - def post(self, request): - user_info = request.data[0] - serializer = self.serializer_class(data = request.data[1]) - if serializer.is_valid(): - serializer.save() - file_id = create_file(uploader = user_info['uploader_name'], uploader_designation = user_info['uploader_designation'], receiver = "vkjain", receiver_designation="CSE HOD", src_module="HR", src_object_id= str(serializer.data['id']), file_extra_JSON= {"type": "CPDAReimbursement"}, attached_file= None) - return Response(serializer.data, status= status.HTTP_200_OK) - else: - return Response(serializer.errors, status= status.HTTP_400_BAD_REQUEST) - - def get(self, request, *args, **kwargs): - pk = request.query_params.get("name") - print(pk) - try: - forms = CPDAReimbursementform.objects.get(name = pk) - serializer = self.serializer_class(forms, many = False) - except MultipleObjectsReturned: - forms = CPDAReimbursementform.objects.filter(name = pk) - serializer = self.serializer_class(forms, many = True) - return Response(serializer.data, status = status.HTTP_200_OK) - - def put(self, request, *args, **kwargs): - pk = request.query_params.get("id") - print(pk) - form = CPDAReimbursementform.objects.get(id = pk) - print(form) - serializer = self.serializer_class(form, data = request.data) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status = status.HTTP_200_OK) - else: - return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST) - -class Leave(APIView): - serializer_class = Leave_serializer - permission_classes = (AllowAny, ) - def post(self, request): - user_info = request.data[0] - serializer = self.serializer_class(data = request.data[1]) - if serializer.is_valid(): - serializer.save() - file_id = create_file(uploader = user_info['uploader_name'], uploader_designation = user_info['uploader_designation'], receiver = "vkjain", receiver_designation="CSE HOD", src_module="HR", src_object_id= str(serializer.data['id']), file_extra_JSON= {"type": "Leave"}, attached_file= None) - return Response(serializer.data, status= status.HTTP_200_OK) - else: - return Response(serializer.errors, status= status.HTTP_400_BAD_REQUEST) - - def get(self, request, *args, **kwargs): - pk = request.query_params.get("name") - print(pk) - try: - forms = Leaveform.objects.get(name = pk) - serializer = self.serializer_class(forms, many = False) - except MultipleObjectsReturned: - forms = Leaveform.objects.filter(name = pk) - serializer = self.serializer_class(forms, many = True) - return Response(serializer.data, status = status.HTTP_200_OK) - - def put(self, request, *args, **kwargs): - pk = request.query_params.get("id") - print(pk) - form = Leaveform.objects.get(id = pk) - print(form) - serializer = self.serializer_class(form, data = request.data) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status = status.HTTP_200_OK) - else: - return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST) - -class Appraisal(APIView): - serializer_class = Appraisal_serializer - permission_classes = (AllowAny, ) - def post(self, request): - user_info = request.data[0] - serializer = self.serializer_class(data = request.data[1]) - if serializer.is_valid(): - serializer.save() - file_id = create_file(uploader = user_info['uploader_name'], uploader_designation = user_info['uploader_designation'], receiver = "vkjain", receiver_designation="CSE HOD", src_module="HR", src_object_id= str(serializer.data['id']), file_extra_JSON= {"type": "Appraisal"}, attached_file= None) - return Response(serializer.data, status= status.HTTP_200_OK) - else: - return Response(serializer.errors, status= status.HTTP_400_BAD_REQUEST) - - def get(self, request, *args, **kwargs): - pk = request.query_params.get("name") - print(pk) - try: - forms = Appraisalform.objects.get(name = pk) - serializer = self.serializer_class(forms, many = False) - except MultipleObjectsReturned: - forms = Appraisalform.objects.filter(name = pk) - serializer = self.serializer_class(forms, many = True) - return Response(serializer.data, status = status.HTTP_200_OK) - - def put(self, request, *args, **kwargs): - pk = request.query_params.get("id") - print(pk) - form = Appraisalform.objects.get(id = pk) - print(form) - serializer = self.serializer_class(form, data = request.data) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status = status.HTTP_200_OK) - else: - return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST) - -class AssignerReviewer(APIView): - def post(self, request, *args, **kwargs): - forward_file(file_id = request.data['file_id'], receiver = "21BCS140", receiver_designation = 'hradmin', remarks = request.data['remarks'], file_extra_JSON = request.data['file_extra_JSON']) - return Response(status = status.HTTP_200_OK) \ No newline at end of file diff --git a/FusionIIIT/applications/hr2/forms.py b/FusionIIIT/applications/hr2/forms.py deleted file mode 100644 index d0b80b92a..000000000 --- a/FusionIIIT/applications/hr2/forms.py +++ /dev/null @@ -1,78 +0,0 @@ -from django import forms -from .models import Employee, EmpConfidentialDetails, ForeignService -from applications.globals.models import ExtraInfo -from django.contrib.auth.forms import UserCreationForm -from django.contrib.auth.models import User - - -class DateInput(forms.DateInput): - input_type = 'date' - - -class EditDetailsForm(forms.ModelForm): - - class Meta: - model = Employee - fields = ['extra_info', 'father_name', 'mother_name', 'religion', 'category', - 'cast', 'home_state', 'home_district', 'date_of_joining', 'designation', 'blood_group'] - - widgets = { - 'date_of_joining': DateInput() - } - - def __init__(self, *args, **kwargs): - super(EditDetailsForm, self).__init__(*args, **kwargs) - - -class EditConfidentialDetailsForm(forms.ModelForm): - - class Meta: - model = EmpConfidentialDetails - fields = ['extra_info', 'aadhar_no', - 'maritial_status', 'bank_account_no', 'salary'] - - def __init__(self, *args, **kwargs): - super(EditConfidentialDetailsForm, self).__init__(*args, **kwargs) - - -class EditServiceBookForm(forms.ModelForm): - - class Meta: - model = ForeignService - fields = ['extra_info', 'start_date', 'end_date', 'job_title', 'organisation', - 'description', 'salary_source', 'designation', 'service_type'] - widgets = {'start_date': DateInput(), 'end_date': DateInput()} - - def __init__(self, *args, **kwargs): - super(EditServiceBookForm, self).__init__(*args, **kwargs) - - -class NewUserForm(UserCreationForm): - first_name = forms.CharField(max_length=50, required=True) - last_name = forms.CharField(max_length=50, required=True) - email = forms.EmailField(required=True) - - class Meta: - model = User - fields = ("username", "email", "password1", - "password2", 'first_name', 'last_name') - - def save(self, commit=True): - user = super(NewUserForm, self).save(commit=False) - user.email = self.cleaned_data['email'] - user.first_name = self.cleaned_data['first_name'] - user.last_name = self.cleaned_data['last_name'] - if commit: - user.save() - return user - - -class AddExtraInfo(forms.ModelForm): - class Meta: - model = ExtraInfo - fields = ['id', 'user', 'title', 'sex', 'date_of_birth', 'title', 'phone_no', - 'address', 'user_type', 'about_me', 'user_status'] - widgets = {'date_of_birth': DateInput()} - - def __init__(self, *args, **kwargs): - super(AddExtraInfo, self).__init__(*args, **kwargs) diff --git a/FusionIIIT/applications/hr2/management/__init__.py b/FusionIIIT/applications/hr2/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/FusionIIIT/applications/hr2/management/commands/__init__.py b/FusionIIIT/applications/hr2/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/FusionIIIT/applications/hr2/management/commands/convert_vl_to_earned.py b/FusionIIIT/applications/hr2/management/commands/convert_vl_to_earned.py new file mode 100644 index 000000000..cab441c09 --- /dev/null +++ b/FusionIIIT/applications/hr2/management/commands/convert_vl_to_earned.py @@ -0,0 +1,41 @@ +from django.core.management.base import BaseCommand +from applications.hr2.services import convert_vl_to_earned + + +class Command(BaseCommand): + help = "Convert unavailed Vacation Leave (VL) to Earned Leave (EL) for faculty at 2:1 for the next year." + + def add_arguments(self, parser): + parser.add_argument( + "--year", + type=int, + default=datetime.date.today().year, + help="Source year to convert from (default: current year).", + ) + parser.add_argument( + "--dry-run", + action="store_true", + dest="dry_run", + help="Show changes without updating balances.", + ) + + def handle(self, *args, **options): + result = convert_vl_to_earned( + source_year=options.get("year"), + dry_run=options.get("dry_run", False), + ) + + if result["dry_run"]: + self.stdout.write( + self.style.WARNING( + "Dry run: would convert VL to EL for " + f"{result['converted_count']} faculty (total EL added: {result['total_converted']})" + ) + ) + else: + self.stdout.write( + self.style.SUCCESS( + "Converted VL to EL for " + f"{result['converted_count']} faculty (total EL added: {result['total_converted']})" + ) + ) diff --git a/FusionIIIT/applications/hr2/management/commands/seed_hr2.py b/FusionIIIT/applications/hr2/management/commands/seed_hr2.py new file mode 100644 index 000000000..b24809d39 --- /dev/null +++ b/FusionIIIT/applications/hr2/management/commands/seed_hr2.py @@ -0,0 +1,14 @@ +from django.core.management.base import BaseCommand +from applications.hr2.services import seed_hr2_demo_data + + +class Command(BaseCommand): + help = "Seed HR2 demo data (employee, access, leave balance)." + + def handle(self, *args, **options): + result = seed_hr2_demo_data() + self.stdout.write( + self.style.SUCCESS( + f"HR2 seed data created/updated for employee {result['employee_id']}." + ) + ) diff --git a/FusionIIIT/applications/hr2/management/commands/seed_hr_demo.py b/FusionIIIT/applications/hr2/management/commands/seed_hr_demo.py new file mode 100644 index 000000000..de4a9842d --- /dev/null +++ b/FusionIIIT/applications/hr2/management/commands/seed_hr_demo.py @@ -0,0 +1,14 @@ +from django.core.management.base import BaseCommand +from applications.hr2.services import seed_hr_demo_data + + +class Command(BaseCommand): + help = "Seed HR demo data for form testing." + + def handle(self, *args, **options): + result = seed_hr_demo_data() + self.stdout.write( + self.style.SUCCESS( + f"HR demo data seeded for {result['employees_seeded']} employee(s)." + ) + ) diff --git a/FusionIIIT/applications/hr2/management/commands/seed_leave_balance.py b/FusionIIIT/applications/hr2/management/commands/seed_leave_balance.py new file mode 100644 index 000000000..2f2556427 --- /dev/null +++ b/FusionIIIT/applications/hr2/management/commands/seed_leave_balance.py @@ -0,0 +1,33 @@ +from django.core.management.base import BaseCommand + +from applications.hr2.services import seed_leave_balances + + +class Command(BaseCommand): + help = "Seed leave balances for testing." + + def add_arguments(self, parser): + parser.add_argument( + "--employee-id", + dest="employee_id", + default="EMP1001", + help="ExtraInfo ID to seed (default: EMP1001)", + ) + parser.add_argument( + "--all", + action="store_true", + dest="seed_all", + help="Seed balances for all ExtraInfo records", + ) + + def handle(self, *args, **options): + result = seed_leave_balances( + employee_id=options.get("employee_id"), + seed_all=options.get("seed_all"), + ) + + self.stdout.write( + self.style.SUCCESS( + f"Leave balances seeded for {result['seeded_count']} employee(s) (year {result['year']})." + ) + ) diff --git a/FusionIIIT/applications/hr2/migrations/0001_initial.py b/FusionIIIT/applications/hr2/migrations/0001_initial.py index 78ce3a457..e20d6a0a9 100644 --- a/FusionIIIT/applications/hr2/migrations/0001_initial.py +++ b/FusionIIIT/applications/hr2/migrations/0001_initial.py @@ -1,7 +1,7 @@ -# Generated by Django 3.1.5 on 2024-07-16 15:44 +# Generated by Django 3.1.5 on 2026-04-05 23:33 +import datetime from django.conf import settings -import django.core.validators from django.db import migrations, models import django.db.models.deletion @@ -11,11 +11,119 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('globals', '0001_initial'), + ('auth', '0012_alter_user_first_name_max_length'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('globals', '0005_moduleaccess_database'), ] operations = [ + migrations.CreateModel( + name='AppraisalPeriod', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('start_date', models.DateField()), + ('end_date', models.DateField()), + ('submission_deadline', models.DateField()), + ('is_active', models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name='Employee', + fields=[ + ('id', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='employee_details', serialize=False, to='auth.user')), + ('father_name', models.CharField(max_length=100)), + ('mother_name', models.CharField(max_length=100)), + ('religion', models.CharField(blank=True, max_length=20, null=True)), + ('category', models.CharField(choices=[('General', 'General'), ('OBC', 'OBC'), ('SC', 'SC'), ('ST', 'ST')], max_length=20)), + ('caste', models.CharField(max_length=50)), + ('home_state', models.CharField(max_length=50)), + ('home_district', models.CharField(max_length=50)), + ('full_address', models.TextField()), + ('date_of_joining', models.DateField()), + ('date_of_birth', models.DateField()), + ('blood_group', models.CharField(choices=[('A+', 'A+'), ('A-', 'A-'), ('B+', 'B+'), ('B-', 'B-'), ('O+', 'O+'), ('O-', 'O-'), ('AB+', 'AB+'), ('AB-', 'AB-')], max_length=3)), + ('phone_number', models.CharField(max_length=15)), + ('personal_email', models.EmailField(max_length=254)), + ('emergency_contact_number', models.CharField(max_length=15)), + ('emergency_contact_name', models.CharField(max_length=100)), + ('employee_type', models.CharField(choices=[('Faculty', 'Faculty'), ('Staff', 'Staff'), ('Other', 'Other')], default='Faculty', max_length=10)), + ], + ), + migrations.CreateModel( + name='EmployeeCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('category_type', models.CharField(choices=[('TEACHING', 'Teaching'), ('NON_TEACHING', 'Non-Teaching')], max_length=20)), + ('pay_level', models.CharField(blank=True, max_length=20)), + ('is_active', models.BooleanField(default=True)), + ], + ), + migrations.CreateModel( + name='LeaveType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('code', models.CharField(max_length=10, unique=True)), + ('max_days_per_year', models.IntegerField(blank=True, null=True)), + ('carry_forward', models.BooleanField(default=False)), + ('max_carry_forward', models.IntegerField(blank=True, null=True)), + ('is_active', models.BooleanField(default=True)), + ], + ), + migrations.CreateModel( + name='QualificationType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('level', models.IntegerField()), + ], + ), + migrations.CreateModel( + name='TrainingProgram', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200)), + ('description', models.TextField()), + ('organizer', models.CharField(max_length=200)), + ('venue', models.CharField(max_length=200)), + ('start_date', models.DateField()), + ('end_date', models.DateField()), + ('max_participants', models.IntegerField(blank=True, null=True)), + ('is_mandatory', models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name='LeaveBalance', + fields=[ + ('empid', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='leave_balance', serialize=False, to='hr2.employee')), + ('casual_leave_taken', models.IntegerField(default=0)), + ('special_casual_leave_taken', models.IntegerField(default=0)), + ('earned_leave_taken', models.IntegerField(default=0)), + ('half_pay_leave_taken', models.IntegerField(default=0)), + ('maternity_leave_taken', models.IntegerField(default=0)), + ('child_care_leave_taken', models.IntegerField(default=0)), + ('paternity_leave_taken', models.IntegerField(default=0)), + ('leave_encashment_taken', models.IntegerField(default=0)), + ('restricted_holiday_taken', models.IntegerField(default=0)), + ], + ), + migrations.CreateModel( + name='LeavePerYear', + fields=[ + ('empid', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='yearly_leave', serialize=False, to='hr2.employee')), + ('casual_leave', models.IntegerField(default=8)), + ('special_casual_leave', models.IntegerField(default=15)), + ('earned_leave', models.IntegerField(default=15)), + ('half_pay_leave', models.IntegerField(default=15)), + ('maternity_leave', models.IntegerField(default=180)), + ('child_care_leave', models.IntegerField(default=730)), + ('paternity_leave', models.IntegerField(default=15)), + ('leave_encashment', models.IntegerField(default=60)), + ('restricted_holiday', models.IntegerField(default=2)), + ], + ), migrations.CreateModel( name='WorkAssignemnt', fields=[ @@ -27,6 +135,102 @@ class Migration(migrations.Migration): ('extra_info', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='globals.extrainfo')), ], ), + migrations.CreateModel( + name='TrainingNomination', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.CharField(choices=[('NOMINATED', 'Nominated'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected'), ('ATTENDED', 'Attended'), ('COMPLETED', 'Completed')], default='NOMINATED', max_length=20)), + ('feedback', models.TextField(blank=True)), + ('certificate', models.FileField(blank=True, upload_to='hr/training/')), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='training_nominations', to='globals.extrainfo')), + ('nominated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='training_nominations_made', to='globals.extrainfo')), + ('program', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hr2.trainingprogram')), + ], + ), + migrations.CreateModel( + name='ServiceHistory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('from_date', models.DateField()), + ('to_date', models.DateField(blank=True, null=True)), + ('pay_scale', models.CharField(blank=True, max_length=50)), + ('basic_pay', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True)), + ('remarks', models.TextField(blank=True)), + ('department', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='globals.departmentinfo')), + ('designation', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='globals.designation')), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='service_history', to='globals.extrainfo')), + ], + options={ + 'ordering': ['-from_date'], + }, + ), + migrations.CreateModel( + name='PromotionApplication', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('application_date', models.DateField()), + ('eligibility_date', models.DateField()), + ('api_score', models.IntegerField(blank=True, null=True)), + ('documents', models.FileField(blank=True, upload_to='hr/promotions/')), + ('status', models.CharField(choices=[('SUBMITTED', 'Submitted'), ('UNDER_REVIEW', 'Under Review'), ('COMMITTEE_STAGE', 'At Committee'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected')], default='SUBMITTED', max_length=20)), + ('remarks', models.TextField(blank=True)), + ('approved_date', models.DateField(blank=True, null=True)), + ('effective_date', models.DateField(blank=True, null=True)), + ('applied_designation', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='applied_promotions', to='globals.designation')), + ('current_designation', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='current_for_promotions', to='globals.designation')), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='promotion_applications', to='globals.extrainfo')), + ], + ), + migrations.CreateModel( + name='ProfessionalQualification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200)), + ('certifying_body', models.CharField(max_length=200)), + ('date_obtained', models.DateField()), + ('valid_until', models.DateField(blank=True, null=True)), + ('document', models.FileField(blank=True, upload_to='hr/professional/')), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='professional_qualifications', to='globals.extrainfo')), + ], + ), + migrations.CreateModel( + name='PreviousExperience', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('organization', models.CharField(max_length=200)), + ('designation', models.CharField(max_length=100)), + ('from_date', models.DateField()), + ('to_date', models.DateField()), + ('experience_type', models.CharField(max_length=50)), + ('description', models.TextField(blank=True)), + ('document', models.FileField(blank=True, upload_to='hr/experience/')), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='previous_experiences', to='globals.extrainfo')), + ], + ), + migrations.CreateModel( + name='PerformanceAppraisalNew', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('teaching_score', models.IntegerField(blank=True, null=True)), + ('research_score', models.IntegerField(blank=True, null=True)), + ('admin_score', models.IntegerField(blank=True, null=True)), + ('extension_score', models.IntegerField(blank=True, null=True)), + ('self_remarks', models.TextField(blank=True)), + ('reviewer_teaching_score', models.IntegerField(blank=True, null=True)), + ('reviewer_research_score', models.IntegerField(blank=True, null=True)), + ('reviewer_admin_score', models.IntegerField(blank=True, null=True)), + ('reviewer_extension_score', models.IntegerField(blank=True, null=True)), + ('reviewer_remarks', models.TextField(blank=True)), + ('final_score', models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True)), + ('final_grade', models.CharField(blank=True, max_length=10)), + ('status', models.CharField(choices=[('DRAFT', 'Draft'), ('SUBMITTED', 'Submitted'), ('REVIEWED', 'Reviewed'), ('APPROVED', 'Approved'), ('FINALIZED', 'Finalized')], default='DRAFT', max_length=20)), + ('submitted_at', models.DateTimeField(blank=True, null=True)), + ('finalized_at', models.DateTimeField(blank=True, null=True)), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='appraisals_new', to='globals.extrainfo')), + ('period', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='hr2.appraisalperiod')), + ('reviewer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='reviewed_performance_appraisals_new', to='globals.extrainfo')), + ], + ), migrations.CreateModel( name='LTCform', fields=[ @@ -34,7 +238,7 @@ class Migration(migrations.Migration): ('employeeId', models.IntegerField()), ('name', models.CharField(max_length=100, null=True)), ('blockYear', models.TextField()), - ('pfNo', models.IntegerField(max_length=50)), + ('pfNo', models.IntegerField()), ('basicPaySalary', models.IntegerField(null=True)), ('designation', models.CharField(max_length=50)), ('departmentInfo', models.CharField(max_length=50)), @@ -63,41 +267,142 @@ class Migration(migrations.Migration): ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='LTC_created_by', to=settings.AUTH_USER_MODEL)), ], ), + migrations.CreateModel( + name='LTCApplicationNew', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('employee_name', models.CharField(max_length=100)), + ('department', models.CharField(max_length=100)), + ('designation', models.CharField(max_length=100)), + ('ltc_block_year', models.IntegerField()), + ('travel_start_date', models.DateField()), + ('travel_end_date', models.DateField()), + ('destination', models.CharField(max_length=200)), + ('purpose_of_travel', models.TextField()), + ('family_members', models.TextField(blank=True)), + ('relationship_details', models.TextField(blank=True)), + ('travel_mode', models.CharField(max_length=50)), + ('ticket_number', models.CharField(blank=True, max_length=100)), + ('ticket_cost', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('accommodation_cost', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('other_expenses', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('total_amount_claimed', models.DecimalField(decimal_places=2, max_digits=10)), + ('tickets_upload', models.CharField(blank=True, max_length=200)), + ('bills_upload', models.CharField(blank=True, max_length=200)), + ('previous_ltc_used', models.BooleanField(default=False)), + ('last_ltc_date', models.DateField(blank=True, null=True)), + ('applied_date', models.DateField(auto_now_add=True)), + ('verified_by_hr', models.BooleanField(default=False)), + ('approval_status', models.CharField(choices=[('PENDING', 'Pending'), ('FORWARDED', 'Forwarded'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected')], default='PENDING', max_length=20)), + ('accountant_status', models.CharField(blank=True, max_length=20)), + ('remarks', models.TextField(blank=True)), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ltc_applications_new', to='globals.extrainfo')), + ], + ), migrations.CreateModel( name='LeaveForm', fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), - ('employeeId', models.IntegerField(max_length=22, null=True)), ('name', models.CharField(max_length=40, null=True)), ('designation', models.CharField(max_length=40, null=True)), - ('submissionDate', models.DateField(blank=True, null=True)), - ('pfNo', models.IntegerField(max_length=30, null=True)), + ('submissionDate', models.DateField(default=datetime.date.today)), + ('personalfileNo', models.CharField(max_length=50, null=True)), ('departmentInfo', models.CharField(max_length=40, null=True)), - ('natureOfLeave', models.TextField(max_length=40, null=True)), ('leaveStartDate', models.DateField(blank=True, null=True)), ('leaveEndDate', models.DateField(blank=True, null=True)), - ('purposeOfLeave', models.TextField(max_length=40, null=True)), - ('addressDuringLeave', models.TextField(blank=True, max_length=40, null=True)), - ('academicResponsibility', models.TextField(blank=True, max_length=40, null=True)), - ('addministrativeResponsibiltyAssigned', models.TextField(max_length=40, null=True)), - ('approved', models.BooleanField(null=True)), + ('Noof_CasualLeave', models.IntegerField(default=0)), + ('Noof_specialCasualLeave', models.IntegerField(default=0)), + ('Noof_earnedLeave', models.IntegerField(default=0)), + ('Noof_commutedLeave', models.IntegerField(default=0)), + ('Noof_restrictedHoliday', models.IntegerField(default=0)), + ('Noof_vacationLeave', models.IntegerField(default=0)), + ('Noof_maternityLeave', models.IntegerField(default=0)), + ('Noof_childCareLeave', models.IntegerField(default=0)), + ('Noof_paternityLeave', models.IntegerField(default=0)), + ('Noof_halfPayLeave', models.IntegerField(default=0)), + ('LeavingStation', models.BooleanField(default=False)), + ('StationLeave_startdate', models.DateField(blank=True, null=True)), + ('StationLeave_enddate', models.DateField(blank=True, null=True)), + ('Address_During_StationLeave', models.TextField(blank=True, null=True)), + ('Purpose_of_leave', models.TextField(blank=True, null=True)), + ('AcademicResponsibility_status', models.CharField(choices=[('Accepted', 'Accepted'), ('Pending', 'Pending'), ('Rejected', 'Rejected')], default='Pending', max_length=10)), + ('AdministrativeResponsibility_status', models.CharField(choices=[('Accepted', 'Accepted'), ('Pending', 'Pending'), ('Rejected', 'Rejected')], default='Pending', max_length=10)), + ('Remarks', models.TextField(blank=True, null=True)), ('approvedDate', models.DateField(auto_now_add=True, null=True)), - ('approved_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='Leave_approved_by', to=settings.AUTH_USER_MODEL)), - ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='Leave_created_by', to=settings.AUTH_USER_MODEL)), + ('status', models.CharField(choices=[('Accepted', 'Accepted'), ('Pending', 'Pending'), ('Rejected', 'Rejected')], default='Pending', max_length=10)), + ('attached_pdf', models.BinaryField(blank=True, null=True)), + ('attached_pdf_name', models.CharField(blank=True, max_length=100, null=True)), + ('file_id', models.IntegerField(blank=True, null=True)), + ('application_type', models.CharField(choices=[('Online', 'Online'), ('Offline', 'Offline')], default='Online', max_length=10)), + ('AcademicResponsibility_designation', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leave_academic_responsibility_designation', to='globals.designation')), + ('AcademicResponsibility_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='academic_responsibility_user', to='hr2.employee')), + ('AdministrativeResponsibility_designation', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leave_administrative_responsibility_designation', to='globals.designation')), + ('AdministrativeResponsibility_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='administrative_responsibility_user', to='hr2.employee')), + ('approved_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leave_approved_by', to='hr2.employee')), + ('approved_by_designation', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leave_approved_by_designation', to='globals.designation')), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leave_applications', to='hr2.employee')), + ('first_recieved_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leave_first_recieved_by', to='hr2.employee')), + ('first_recieved_designation', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leave_first_recieved_designation', to='globals.designation')), ], ), migrations.CreateModel( - name='LeaveBalance', + name='LeaveClaim', fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), - ('casualLeave', models.IntegerField(default=0)), - ('specialCasualLeave', models.IntegerField(default=0)), - ('earnedLeave', models.IntegerField(default=0)), - ('commutedLeave', models.IntegerField(default=0)), - ('restrictedHoliday', models.IntegerField(default=0)), - ('stationLeave', models.IntegerField(default=0)), - ('vacationLeave', models.IntegerField(default=0)), - ('employeeId', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='globals.extrainfo')), + ('claim_date', models.DateField(default=datetime.date.today)), + ('leaveStartDate', models.DateField(blank=True, null=True)), + ('leaveEndDate', models.DateField(blank=True, null=True)), + ('Noof_CasualLeave', models.IntegerField(default=0)), + ('Noof_specialCasualLeave', models.IntegerField(default=0)), + ('Noof_earnedLeave', models.IntegerField(default=0)), + ('Noof_commutedLeave', models.IntegerField(default=0)), + ('Noof_restrictedHoliday', models.IntegerField(default=0)), + ('Noof_vacationLeave', models.IntegerField(default=0)), + ('Noof_maternityLeave', models.IntegerField(default=0)), + ('Noof_childCareLeave', models.IntegerField(default=0)), + ('Noof_paternityLeave', models.IntegerField(default=0)), + ('Noof_halfPayLeave', models.IntegerField(default=0)), + ('remarks', models.TextField(blank=True, null=True)), + ('approvedDate', models.DateField(auto_now_add=True, null=True)), + ('status', models.CharField(choices=[('Accepted', 'Accepted'), ('Pending', 'Pending'), ('Rejected', 'Rejected')], default='Pending', max_length=10)), + ('attached_pdf', models.BinaryField(blank=True, null=True)), + ('attached_pdf_name', models.CharField(blank=True, max_length=100, null=True)), + ('file_id', models.IntegerField(blank=True, null=True)), + ('application_type', models.CharField(choices=[('Online', 'Online'), ('Offline', 'Offline')], default='Online', max_length=10)), + ('approved_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leave_claim_approved_by', to='hr2.employee')), + ('approved_by_designation', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leave_claim_approved_by_designation', to='globals.designation')), + ('leave_form', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leave_claims', to='hr2.leaveform')), + ], + options={ + 'verbose_name': 'Leave Claim', + 'verbose_name_plural': 'Leave Claims', + }, + ), + migrations.CreateModel( + name='LeaveApplicationNew', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('employee_name', models.CharField(max_length=100)), + ('department', models.CharField(max_length=100)), + ('designation', models.CharField(max_length=100)), + ('leave_type', models.CharField(choices=[('Casual', 'Casual'), ('Restricted', 'Restricted'), ('Medical', 'Medical'), ('Earned', 'Earned'), ('Vacation', 'Vacation'), ('Sabbatical', 'Sabbatical')], max_length=30)), + ('start_date', models.DateField()), + ('end_date', models.DateField()), + ('total_days', models.DecimalField(decimal_places=1, max_digits=5)), + ('reason', models.TextField()), + ('contact_during_leave', models.CharField(max_length=15)), + ('address_during_leave', models.TextField()), + ('handover_to', models.CharField(max_length=100)), + ('handover_notes', models.TextField(blank=True)), + ('medical_certificate', models.CharField(blank=True, max_length=200)), + ('attachment_file', models.CharField(blank=True, max_length=200)), + ('applied_date', models.DateField(auto_now_add=True)), + ('leave_balance_before', models.DecimalField(blank=True, decimal_places=1, max_digits=5, null=True)), + ('leave_balance_after', models.DecimalField(blank=True, decimal_places=1, max_digits=5, null=True)), + ('approval_status', models.CharField(choices=[('PENDING', 'Pending'), ('FORWARDED', 'Forwarded'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected')], default='PENDING', max_length=20)), + ('current_approver_role', models.CharField(blank=True, max_length=50)), + ('remarks', models.TextField(blank=True)), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leave_applications_new', to='globals.extrainfo')), ], ), migrations.CreateModel( @@ -116,42 +421,55 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name='Employee', + name='EmployeeDetailsExtended', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('father_name', models.CharField(default='', max_length=40)), - ('mother_name', models.CharField(default='', max_length=40)), - ('religion', models.CharField(default='', max_length=40)), - ('category', models.CharField(choices=[('SC', 'SC'), ('ST', 'ST'), ('OBC', 'OBC'), ('GENERAL', 'GENERAL'), ('PWD', 'PWD')], max_length=50)), - ('cast', models.CharField(default='', max_length=40)), - ('home_state', models.CharField(default='', max_length=40)), - ('home_district', models.CharField(default='', max_length=40)), + ('father_name', models.CharField(blank=True, max_length=100)), + ('mother_name', models.CharField(blank=True, max_length=100)), + ('spouse_name', models.CharField(blank=True, max_length=100)), + ('marital_status', models.CharField(blank=True, choices=[('SINGLE', 'Single'), ('MARRIED', 'Married'), ('WIDOWED', 'Widowed'), ('DIVORCED', 'Divorced')], max_length=20)), + ('pan_number', models.CharField(blank=True, max_length=15)), + ('aadhar_number', models.CharField(blank=True, max_length=15)), + ('passport_number', models.CharField(blank=True, max_length=20)), ('date_of_joining', models.DateField(blank=True, null=True)), - ('designation', models.CharField(default='', max_length=40)), - ('blood_group', models.CharField(choices=[('AB+', 'AB+'), ('O+', 'O+'), ('AB-', 'AB-'), ('B+', 'B+'), ('B-', 'B-'), ('O-', 'O-'), ('A+', 'A+'), ('A-', 'A-')], max_length=50)), - ('extra_info', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='globals.extrainfo')), + ('date_of_superannuation', models.DateField(blank=True, null=True)), + ('appointment_type', models.CharField(blank=True, max_length=50)), + ('employee_status', models.CharField(choices=[('ACTIVE', 'Active'), ('ON_LEAVE', 'On Leave'), ('DEPUTATION', 'On Deputation'), ('SUSPENDED', 'Suspended'), ('RETIRED', 'Retired'), ('RESIGNED', 'Resigned'), ('TERMINATED', 'Terminated')], default='ACTIVE', max_length=20)), + ('bank_name', models.CharField(blank=True, max_length=100)), + ('bank_account', models.CharField(blank=True, max_length=30)), + ('ifsc_code', models.CharField(blank=True, max_length=15)), + ('emergency_contact_name', models.CharField(blank=True, max_length=100)), + ('emergency_contact_phone', models.CharField(blank=True, max_length=15)), + ('emergency_contact_relation', models.CharField(blank=True, max_length=50)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='hr2.employeecategory')), + ('extra_info', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='employee_details_extended', to='globals.extrainfo')), ], ), migrations.CreateModel( name='EmpDependents', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='', max_length=100)), - ('gender', models.CharField(choices=[('M', 'Male'), ('F', 'Female'), ('O', 'Other')], max_length=50)), - ('dob', models.DateField(max_length=6, null=True)), - ('relationship', models.CharField(default='', max_length=40)), - ('extra_info', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='globals.extrainfo')), + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100)), + ('gender', models.CharField(choices=[('Male', 'Male'), ('Female', 'Female'), ('Other', 'Other')], max_length=10)), + ('relation', models.CharField(max_length=50)), + ('contact_number', models.CharField(max_length=15)), + ('contact_email', models.EmailField(blank=True, max_length=254, null=True)), + ('date_of_birth', models.DateField()), + ('empid', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dependents', to='hr2.employee')), ], ), migrations.CreateModel( name='EmpConfidentialDetails', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('aadhar_no', models.BigIntegerField(default=0, max_length=12, validators=[django.core.validators.MaxValueValidator(999999999999), django.core.validators.MinValueValidator(99999999999)])), - ('maritial_status', models.CharField(choices=[('MARRIED', 'MARRIED'), ('UN-MARRIED', 'UN-MARRIED'), ('WIDOW', 'WIDOW')], max_length=50)), - ('bank_account_no', models.IntegerField(default=0)), - ('salary', models.IntegerField(default=0)), - ('extra_info', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='globals.extrainfo')), + ('id', models.AutoField(primary_key=True, serialize=False)), + ('aadhar_number', models.CharField(max_length=12, unique=True)), + ('pan_number', models.CharField(max_length=10, unique=True)), + ('marital_status', models.CharField(choices=[('Single', 'Single'), ('Married', 'Married'), ('Divorced', 'Divorced'), ('Widowed', 'Widowed')], max_length=10)), + ('personal_file_number', models.CharField(max_length=50, unique=True)), + ('bank_account_number', models.CharField(max_length=20, unique=True)), + ('ifsc_code', models.CharField(max_length=20, null=True)), + ('basic_pay', models.DecimalField(decimal_places=2, max_digits=10)), + ('empid', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='confidential_details', to='hr2.employee')), ], ), migrations.CreateModel( @@ -163,14 +481,60 @@ class Migration(migrations.Migration): ('extra_info', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='globals.extrainfo')), ], ), + migrations.CreateModel( + name='EducationalQualification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('degree', models.CharField(max_length=100)), + ('specialization', models.CharField(blank=True, max_length=200)), + ('institution', models.CharField(max_length=200)), + ('university', models.CharField(blank=True, max_length=200)), + ('year_of_passing', models.IntegerField()), + ('division_grade', models.CharField(blank=True, max_length=50)), + ('document', models.FileField(blank=True, upload_to='hr/qualifications/')), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='qualifications', to='globals.extrainfo')), + ('qualification_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='hr2.qualificationtype')), + ], + ), + migrations.CreateModel( + name='CPDAReimbursementNew', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('employee_name', models.CharField(max_length=100)), + ('department', models.CharField(max_length=100)), + ('designation', models.CharField(max_length=100)), + ('event_name', models.CharField(max_length=200)), + ('event_type', models.CharField(max_length=50)), + ('organized_by', models.CharField(blank=True, max_length=200)), + ('venue', models.CharField(blank=True, max_length=200)), + ('start_date', models.DateField()), + ('end_date', models.DateField()), + ('registration_fee', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('travel_expense', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('accommodation_expense', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('other_expenses', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('total_amount', models.DecimalField(decimal_places=2, max_digits=10)), + ('purpose_of_attending', models.TextField()), + ('benefits_to_institution', models.TextField()), + ('invitation_letter', models.CharField(blank=True, max_length=200)), + ('receipts', models.CharField(blank=True, max_length=200)), + ('certificates', models.CharField(blank=True, max_length=200)), + ('applied_date', models.DateField(auto_now_add=True)), + ('verified_by_hr', models.BooleanField(default=False)), + ('approval_status', models.CharField(choices=[('PENDING', 'Pending'), ('FORWARDED', 'Forwarded'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected')], default='PENDING', max_length=20)), + ('accountant_processing_status', models.CharField(blank=True, max_length=30)), + ('remarks', models.TextField(blank=True)), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cpda_reimbursements_new', to='globals.extrainfo')), + ], + ), migrations.CreateModel( name='CPDAReimbursementform', fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), - ('employeeId', models.IntegerField(max_length=22, null=True)), + ('employeeId', models.IntegerField(null=True)), ('name', models.CharField(max_length=50)), ('designation', models.CharField(max_length=50)), - ('pfNo', models.IntegerField(max_length=20)), + ('pfNo', models.IntegerField()), ('advanceTaken', models.IntegerField()), ('purpose', models.TextField()), ('adjustmentSubmitted', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), @@ -178,23 +542,54 @@ class Migration(migrations.Migration): ('advanceDueAdjustment', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), ('advanceAmountPDA', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), ('amountCheckedInPDA', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), - ('submissionDate', models.DateField(auto_now_add=True)), + ('submissionDate', models.DateField(blank=True, null=True)), ('approved', models.BooleanField(null=True)), ('approvedDate', models.DateField(auto_now_add=True, null=True)), ('approved_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='CPDAR_approved_by', to=settings.AUTH_USER_MODEL)), ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='CPDAR_created_by', to=settings.AUTH_USER_MODEL)), ], ), + migrations.CreateModel( + name='CPDAAdvanceNew', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('employee_name', models.CharField(max_length=100)), + ('department', models.CharField(max_length=100)), + ('designation', models.CharField(max_length=100)), + ('event_name', models.CharField(max_length=200)), + ('event_type', models.CharField(max_length=50)), + ('organized_by', models.CharField(blank=True, max_length=200)), + ('venue', models.CharField(blank=True, max_length=200)), + ('start_date', models.DateField()), + ('end_date', models.DateField()), + ('registration_fee', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('travel_expense', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('accommodation_expense', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('other_expenses', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('total_amount', models.DecimalField(decimal_places=2, max_digits=10)), + ('purpose_of_attending', models.TextField()), + ('benefits_to_institution', models.TextField()), + ('invitation_letter', models.CharField(blank=True, max_length=200)), + ('receipts', models.CharField(blank=True, max_length=200)), + ('certificates', models.CharField(blank=True, max_length=200)), + ('applied_date', models.DateField(auto_now_add=True)), + ('verified_by_hr', models.BooleanField(default=False)), + ('approval_status', models.CharField(choices=[('PENDING', 'Pending'), ('FORWARDED', 'Forwarded'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected')], default='PENDING', max_length=20)), + ('accountant_processing_status', models.CharField(blank=True, max_length=30)), + ('remarks', models.TextField(blank=True)), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cpda_advances_new', to='globals.extrainfo')), + ], + ), migrations.CreateModel( name='CPDAAdvanceform', fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), - ('employeeId', models.IntegerField(max_length=22, null=True)), + ('employeeId', models.IntegerField(null=True)), ('name', models.CharField(max_length=40, null=True)), ('designation', models.CharField(max_length=40, null=True)), - ('pfNo', models.IntegerField(max_length=30, null=True)), + ('pfNo', models.IntegerField(null=True)), ('purpose', models.TextField(max_length=40, null=True)), - ('amountRequired', models.IntegerField(max_length=30, null=True)), + ('amountRequired', models.IntegerField(null=True)), ('advanceDueAdjustment', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), ('submissionDate', models.DateField(blank=True, null=True)), ('balanceAvailable', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), @@ -202,15 +597,47 @@ class Migration(migrations.Migration): ('amountCheckedInPDA', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), ('approved', models.BooleanField(null=True)), ('approvedDate', models.DateField(auto_now_add=True, null=True)), - ('approved_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='CPDA_approved_by', to=settings.AUTH_USER_MODEL)), + ('approved_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='CPDA_approved_by', to=settings.AUTH_USER_MODEL)), ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='CPDA_created_by', to=settings.AUTH_USER_MODEL)), ], ), + migrations.CreateModel( + name='AppraisalFormNew', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('employee_name', models.CharField(max_length=100)), + ('department', models.CharField(max_length=100)), + ('designation', models.CharField(max_length=100)), + ('appraisal_year', models.CharField(max_length=20)), + ('self_summary', models.TextField()), + ('key_responsibilities', models.TextField()), + ('achievements', models.TextField()), + ('challenges_faced', models.TextField(blank=True)), + ('teaching_performance', models.TextField(blank=True)), + ('research_work', models.TextField(blank=True)), + ('publications', models.TextField(blank=True)), + ('projects_handled', models.TextField(blank=True)), + ('administrative_contributions', models.TextField(blank=True)), + ('trainings_attended', models.TextField(blank=True)), + ('certifications', models.TextField(blank=True)), + ('workshops', models.TextField(blank=True)), + ('goals_achieved', models.TextField()), + ('future_goals', models.TextField()), + ('supporting_documents', models.CharField(blank=True, max_length=200)), + ('reviewer_id', models.CharField(blank=True, max_length=50)), + ('reviewer_comments', models.TextField(blank=True)), + ('rating', models.CharField(blank=True, max_length=20)), + ('status', models.CharField(choices=[('PENDING', 'Pending'), ('REVIEWED', 'Reviewed'), ('APPROVED', 'Approved')], default='PENDING', max_length=20)), + ('remarks', models.TextField(blank=True)), + ('submitted_at', models.DateTimeField(auto_now_add=True)), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='appraisal_forms_new', to='globals.extrainfo')), + ], + ), migrations.CreateModel( name='Appraisalform', fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), - ('employeeId', models.IntegerField(max_length=22, null=True)), + ('employeeId', models.IntegerField(null=True)), ('name', models.CharField(max_length=22)), ('designation', models.CharField(max_length=50)), ('disciplineInfo', models.CharField(max_length=22, null=True)), @@ -243,4 +670,53 @@ class Migration(migrations.Migration): ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='Appraisal_created_by', to=settings.AUTH_USER_MODEL)), ], ), + migrations.CreateModel( + name='FacultyWorkload', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('semester', models.CharField(max_length=20)), + ('year', models.IntegerField()), + ('lecture_hours', models.IntegerField(default=0)), + ('tutorial_hours', models.IntegerField(default=0)), + ('lab_hours', models.IntegerField(default=0)), + ('total_hours', models.IntegerField(default=0)), + ('total_students', models.IntegerField(default=0)), + ('phd_scholars', models.IntegerField(default=0)), + ('faculty', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workloads', to='globals.faculty')), + ], + options={ + 'unique_together': {('faculty', 'semester', 'year')}, + }, + ), + migrations.CreateModel( + name='EmployeeLeaveBalance', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('year', models.IntegerField()), + ('opening_balance', models.DecimalField(decimal_places=1, default=0, max_digits=5)), + ('accrued', models.DecimalField(decimal_places=1, default=0, max_digits=5)), + ('availed', models.DecimalField(decimal_places=1, default=0, max_digits=5)), + ('current_balance', models.DecimalField(decimal_places=1, default=0, max_digits=5)), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leave_balances_new', to='globals.extrainfo')), + ('leave_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='hr2.leavetype')), + ], + options={ + 'unique_together': {('employee', 'leave_type', 'year')}, + }, + ), + migrations.CreateModel( + name='EmployeeAttendance', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField()), + ('status', models.CharField(choices=[('PRESENT', 'Present'), ('ABSENT', 'Absent'), ('HALF_DAY', 'Half Day'), ('ON_LEAVE', 'On Leave'), ('ON_TOUR', 'On Tour'), ('WORK_FROM_HOME', 'WFH')], max_length=20)), + ('in_time', models.TimeField(blank=True, null=True)), + ('out_time', models.TimeField(blank=True, null=True)), + ('remarks', models.TextField(blank=True)), + ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attendance_records', to='globals.extrainfo')), + ], + options={ + 'unique_together': {('employee', 'date')}, + }, + ), ] diff --git a/FusionIIIT/applications/hr2/migrations/0002_auto_20241020_1126.py b/FusionIIIT/applications/hr2/migrations/0002_auto_20241020_1126.py deleted file mode 100644 index 5b99015f7..000000000 --- a/FusionIIIT/applications/hr2/migrations/0002_auto_20241020_1126.py +++ /dev/null @@ -1,64 +0,0 @@ -# Generated by Django 3.1.5 on 2024-10-20 11:26 - -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('hr2', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='appraisalform', - name='employeeId', - field=models.IntegerField(null=True), - ), - migrations.AlterField( - model_name='cpdaadvanceform', - name='amountRequired', - field=models.IntegerField(null=True), - ), - migrations.AlterField( - model_name='cpdaadvanceform', - name='employeeId', - field=models.IntegerField(null=True), - ), - migrations.AlterField( - model_name='cpdaadvanceform', - name='pfNo', - field=models.IntegerField(null=True), - ), - migrations.AlterField( - model_name='cpdareimbursementform', - name='employeeId', - field=models.IntegerField(null=True), - ), - migrations.AlterField( - model_name='cpdareimbursementform', - name='pfNo', - field=models.IntegerField(), - ), - migrations.AlterField( - model_name='empconfidentialdetails', - name='aadhar_no', - field=models.BigIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(999999999999), django.core.validators.MinValueValidator(99999999999)]), - ), - migrations.AlterField( - model_name='leaveform', - name='employeeId', - field=models.IntegerField(null=True), - ), - migrations.AlterField( - model_name='leaveform', - name='pfNo', - field=models.IntegerField(null=True), - ), - migrations.AlterField( - model_name='ltcform', - name='pfNo', - field=models.IntegerField(), - ), - ] diff --git a/FusionIIIT/applications/hr2/migrations/0002_leave_nominee.py b/FusionIIIT/applications/hr2/migrations/0002_leave_nominee.py new file mode 100644 index 000000000..6c4f771a5 --- /dev/null +++ b/FusionIIIT/applications/hr2/migrations/0002_leave_nominee.py @@ -0,0 +1,26 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hr2', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='leaveapplicationnew', + name='handover_to', + field=models.CharField(blank=True, max_length=100), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='nominee_status', + field=models.CharField(choices=[('NOT_REQUIRED', 'Not Required'), ('PENDING', 'Pending'), ('ACCEPTED', 'Accepted'), ('DECLINED', 'Declined')], default='NOT_REQUIRED', max_length=20), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='nominee_responded_at', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/FusionIIIT/applications/hr2/migrations/0003_leave_document_request.py b/FusionIIIT/applications/hr2/migrations/0003_leave_document_request.py new file mode 100644 index 000000000..827007000 --- /dev/null +++ b/FusionIIIT/applications/hr2/migrations/0003_leave_document_request.py @@ -0,0 +1,39 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('hr2', '0002_leave_nominee'), + ] + + operations = [ + migrations.AddField( + model_name='leaveapplicationnew', + name='document_request_message', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='document_request_status', + field=models.CharField( + choices=[('NOT_REQUESTED', 'Not Requested'), ('REQUESTED', 'Requested'), ('SUBMITTED', 'Submitted')], + default='NOT_REQUESTED', + max_length=20, + ), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='document_requested_at', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='document_submission', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='document_submitted_at', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/FusionIIIT/applications/hr2/migrations/0004_leave_cancellation.py b/FusionIIIT/applications/hr2/migrations/0004_leave_cancellation.py new file mode 100644 index 000000000..4ca5c3bf3 --- /dev/null +++ b/FusionIIIT/applications/hr2/migrations/0004_leave_cancellation.py @@ -0,0 +1,66 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hr2', '0003_leave_document_request'), + ] + + operations = [ + migrations.AddField( + model_name='leaveapplicationnew', + name='cancel_status', + field=models.CharField( + choices=[('NOT_REQUESTED', 'Not Requested'), ('REQUESTED', 'Requested'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected')], + default='NOT_REQUESTED', + max_length=20, + ), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='cancel_requested_at', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='cancel_decided_at', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='cancel_requested_by_role', + field=models.CharField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='cancel_current_approver_role', + field=models.CharField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='cancel_reason', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='cancel_decision_remarks', + field=models.TextField(blank=True), + ), + migrations.AlterField( + model_name='leaveapplicationnew', + name='approval_status', + field=models.CharField( + choices=[ + ('PENDING', 'Pending'), + ('FORWARDED', 'Forwarded'), + ('APPROVED', 'Approved'), + ('REJECTED', 'Rejected'), + ('WITHDRAWN', 'Withdrawn'), + ('CANCELLED', 'Cancelled'), + ], + default='PENDING', + max_length=20, + ), + ), + ] diff --git a/FusionIIIT/applications/hr2/migrations/0005_leave_extension.py b/FusionIIIT/applications/hr2/migrations/0005_leave_extension.py new file mode 100644 index 000000000..00bfa44af --- /dev/null +++ b/FusionIIIT/applications/hr2/migrations/0005_leave_extension.py @@ -0,0 +1,60 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hr2', '0004_leave_cancellation'), + ] + + operations = [ + migrations.AddField( + model_name='leaveapplicationnew', + name='extension_status', + field=models.CharField( + choices=[('NOT_REQUESTED', 'Not Requested'), ('REQUESTED', 'Requested'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected')], + default='NOT_REQUESTED', + max_length=20, + ), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='extension_requested_at', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='extension_decided_at', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='extension_requested_by_role', + field=models.CharField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='extension_current_approver_role', + field=models.CharField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='extension_reason', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='extension_new_end_date', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='extension_new_total_days', + field=models.DecimalField(blank=True, decimal_places=1, max_digits=5, null=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='extension_decision_remarks', + field=models.TextField(blank=True), + ), + ] diff --git a/FusionIIIT/applications/hr2/migrations/0006_station_leave_selection.py b/FusionIIIT/applications/hr2/migrations/0006_station_leave_selection.py new file mode 100644 index 000000000..53149b7f6 --- /dev/null +++ b/FusionIIIT/applications/hr2/migrations/0006_station_leave_selection.py @@ -0,0 +1,20 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hr2', '0005_leave_extension'), + ] + + operations = [ + migrations.AddField( + model_name='leaveapplicationnew', + name='station_leave', + field=models.CharField( + blank=True, + choices=[('WITH', 'With Station Leave'), ('WITHOUT', 'Without Station Leave')], + max_length=10, + ), + ), + ] diff --git a/FusionIIIT/applications/hr2/migrations/0007_half_day_cl.py b/FusionIIIT/applications/hr2/migrations/0007_half_day_cl.py new file mode 100644 index 000000000..3757b3f1c --- /dev/null +++ b/FusionIIIT/applications/hr2/migrations/0007_half_day_cl.py @@ -0,0 +1,25 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hr2', '0006_station_leave_selection'), + ] + + operations = [ + migrations.AddField( + model_name='leaveapplicationnew', + name='is_half_day', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='half_day_slot', + field=models.CharField( + blank=True, + choices=[('AM', 'AM'), ('PM', 'PM')], + max_length=2, + ), + ), + ] diff --git a/FusionIIIT/applications/hr2/migrations/0008_station_leave_length.py b/FusionIIIT/applications/hr2/migrations/0008_station_leave_length.py new file mode 100644 index 000000000..2b38ec128 --- /dev/null +++ b/FusionIIIT/applications/hr2/migrations/0008_station_leave_length.py @@ -0,0 +1,20 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hr2', '0007_half_day_cl'), + ] + + operations = [ + migrations.AlterField( + model_name='leaveapplicationnew', + name='station_leave', + field=models.CharField( + blank=True, + choices=[('WITH', 'With Station Leave'), ('WITHOUT', 'Without Station Leave'), ('NOT_REQUIRED', 'Not Required')], + max_length=12, + ), + ), + ] diff --git a/FusionIIIT/applications/hr2/migrations/0009_leave_resumption.py b/FusionIIIT/applications/hr2/migrations/0009_leave_resumption.py new file mode 100644 index 000000000..11ea10ab8 --- /dev/null +++ b/FusionIIIT/applications/hr2/migrations/0009_leave_resumption.py @@ -0,0 +1,50 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hr2', '0008_station_leave_length'), + ] + + operations = [ + migrations.AddField( + model_name='leaveapplicationnew', + name='resumption_status', + field=models.CharField( + choices=[('NOT_REQUESTED', 'Not Requested'), ('SUBMITTED', 'Submitted'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected')], + default='NOT_REQUESTED', + max_length=20, + ), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='resumption_date', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='resumption_reason', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='resumption_submitted_at', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='resumption_decided_at', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='resumption_current_approver_role', + field=models.CharField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='leaveapplicationnew', + name='resumption_decision_remarks', + field=models.TextField(blank=True), + ), + ] diff --git a/FusionIIIT/applications/hr2/migrations/0010_appraisal_assignment.py b/FusionIIIT/applications/hr2/migrations/0010_appraisal_assignment.py new file mode 100644 index 000000000..1e6959469 --- /dev/null +++ b/FusionIIIT/applications/hr2/migrations/0010_appraisal_assignment.py @@ -0,0 +1,43 @@ +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("hr2", "0009_leave_resumption"), + ] + + operations = [ + migrations.AddField( + model_name="appraisalformnew", + name="assigned_reviewer_role", + field=models.CharField(blank=True, max_length=20), + ), + migrations.AddField( + model_name="appraisalformnew", + name="assigned_reviewer", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="assigned_appraisals_new", + to="globals.extrainfo", + ), + ), + migrations.AddField( + model_name="appraisalformnew", + name="assigned_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="appraisal_assignments_made", + to="globals.extrainfo", + ), + ), + migrations.AddField( + model_name="appraisalformnew", + name="assigned_at", + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/FusionIIIT/applications/hr2/models.py b/FusionIIIT/applications/hr2/models.py index e225ca076..22b7fa5cc 100644 --- a/FusionIIIT/applications/hr2/models.py +++ b/FusionIIIT/applications/hr2/models.py @@ -1,10 +1,13 @@ from django.db import models -from applications.globals.models import ExtraInfo -from django.core.validators import MaxValueValidator, MinValueValidator +from applications.globals.models import ExtraInfo, Designation, DepartmentInfo, Faculty, Staff +# from django.core.validators import MaxValueValidator, MinValueValidator from django.contrib.auth.models import User +from datetime import date +from applications.filetracking.models import File + +# ==================== EXISTING MODELS (KEPT FOR BACKWARD COMPATIBILITY) ==================== class Constants: - # Class for various choices on the enumerations GENDER_CHOICES = ( ('M', 'Male'), ('F', 'Female'), @@ -22,15 +25,12 @@ class Constants: ('OBC', 'OBC'), ('GENERAL', 'GENERAL'), ('PWD', 'PWD'), - ) MARITIAL_STATUS = ( ('MARRIED', 'MARRIED'), ('UN-MARRIED', 'UN-MARRIED'), ('WIDOW', 'WIDOW'), - ) - BLOOD_GROUP = ( ('AB+', 'AB+'), ('O+', 'O+'), @@ -40,7 +40,6 @@ class Constants: ('O-', 'O-'), ('A+', 'A+'), ('A-', 'A-'), - ) FOREIGN_SERVICE = ( ('LIEN', 'LIEN'), @@ -48,67 +47,91 @@ class Constants: ('OTHER', 'OTHER'), ) - - -# Employee model +# Employee Table class Employee(models.Model): - """ - table for employee details - """ - extra_info = models.OneToOneField(ExtraInfo, on_delete=models.CASCADE) - father_name = models.CharField(max_length=40, default='') - mother_name = models.CharField(max_length=40, default='') - religion = models.CharField(max_length=40, default='') - category = models.CharField(max_length=50, null=False, choices=Constants.CATEGORY) - cast = models.CharField(max_length=40, default='') - home_state = models.CharField(max_length=40, default='') - home_district = models.CharField(max_length=40, default='') - date_of_joining = models.DateField(null=True, blank=True) - designation = models.CharField(max_length=40, default='') - blood_group = models.CharField( - max_length=50, choices=Constants.BLOOD_GROUP) + id = models.OneToOneField(User, on_delete=models.CASCADE, related_name='employee_details', primary_key=True) + father_name = models.CharField(max_length=100) + mother_name = models.CharField(max_length=100) + religion = models.CharField(max_length=20, null=True, blank=True) + CATEGORY_CHOICES = [ + ('General', 'General'), + ('OBC', 'OBC'), + ('SC', 'SC'), + ('ST', 'ST'), + ] + category = models.CharField(max_length=20, choices=CATEGORY_CHOICES) + caste = models.CharField(max_length=50) + home_state = models.CharField(max_length=50) + home_district = models.CharField(max_length=50) + full_address = models.TextField() + date_of_joining = models.DateField() + date_of_birth = models.DateField() + BLOOD_GROUP_CHOICES = [ + ('A+', 'A+'), + ('A-', 'A-'), + ('B+', 'B+'), + ('B-', 'B-'), + ('O+', 'O+'), + ('O-', 'O-'), + ('AB+', 'AB+'), + ('AB-', 'AB-'), + ] + blood_group = models.CharField(max_length=3, choices=BLOOD_GROUP_CHOICES) + phone_number = models.CharField(max_length=15) + personal_email = models.EmailField() + emergency_contact_number = models.CharField(max_length=15) + emergency_contact_name = models.CharField(max_length=100) + Employee_Type = [ + ('Faculty', 'Faculty'), + ('Staff', 'Staff'), + ('Other', 'Other'), + ] + employee_type = models.CharField(max_length=10, choices=Employee_Type, default='Faculty') def __str__(self): - return self.extra_info.user.first_name - + return f"{self.id.username} - Employee Details" -# table for employee confidential details +# Employee Confidential Table class EmpConfidentialDetails(models.Model): - """ - table for employee confidential details - """ - extra_info = models.OneToOneField(ExtraInfo, on_delete=models.CASCADE) - aadhar_no = models.BigIntegerField(default=0, - validators=[MaxValueValidator(999999999999),MinValueValidator(99999999999)]) - - maritial_status = models.CharField( - max_length=50, null=False, choices=Constants.MARITIAL_STATUS) - bank_account_no = models.IntegerField(default=0) - salary = models.IntegerField(default=0) + id = models.AutoField(primary_key=True) + empid = models.OneToOneField(Employee, on_delete=models.CASCADE, related_name='confidential_details') + aadhar_number = models.CharField(max_length=12, unique=True) + pan_number = models.CharField(max_length=10, unique=True) + MARITAL_STATUS_CHOICES = [ + ('Single', 'Single'), + ('Married', 'Married'), + ('Divorced', 'Divorced'), + ('Widowed', 'Widowed'), + ] + marital_status = models.CharField(max_length=10, choices=MARITAL_STATUS_CHOICES) + personal_file_number = models.CharField(max_length=50, unique=True) + bank_account_number = models.CharField(max_length=20, unique=True) + ifsc_code = models.CharField(max_length=20, null=True) + basic_pay = models.DecimalField(max_digits=10, decimal_places=2) def __str__(self): - return self.extra_info.user.first_name - -# table for employee's dependent details - + return f"Confidential Details of {self.empid.id.username}" +# Employee Dependents Table class EmpDependents(models.Model): - """Table for employee's dependent details """ - extra_info = models.OneToOneField(ExtraInfo, on_delete=models.CASCADE) - name = models.CharField(max_length=100, default='') - gender = models.CharField(max_length=50, choices=Constants.GENDER_CHOICES) - dob = models.DateField(max_length=6, null=True) - relationship = models.CharField(max_length=40, default='') + id = models.AutoField(primary_key=True) + empid = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name='dependents') + name = models.CharField(max_length=100) + GENDER_CHOICES = [ + ('Male', 'Male'), + ('Female', 'Female'), + ('Other', 'Other'), + ] + gender = models.CharField(max_length=10, choices=GENDER_CHOICES) + relation = models.CharField(max_length=50) + contact_number = models.CharField(max_length=15) + contact_email = models.EmailField(null=True, blank=True) + date_of_birth = models.DateField() def __str__(self): - return self.extra_info.user.first_name - + return f"Dependent {self.name} of {self.empid.id.username}" class ForeignService(models.Model): - """ - This table contains details about deputation, lien - and other foreign services of employee - """ extra_info = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE) start_date = models.DateField(max_length=6, null=True, blank=True) end_date = models.DateField(max_length=6, null=True, blank=True) @@ -117,26 +140,19 @@ class ForeignService(models.Model): description = models.CharField(max_length=300, default='') salary_source = models.CharField(max_length=100, default='') designation = models.CharField(max_length=100, default='') - # award_name = models.CharField(max_length=100, default='') - # award_type = models.CharField(max_length=100, default='') - # achievement_date = models.CharField(max_length=100, default='') - service_type = models.CharField( - max_length=100, choices=Constants.FOREIGN_SERVICE) + service_type = models.CharField(max_length=100, choices=Constants.FOREIGN_SERVICE) def __str__(self): return self.extra_info.user.first_name - class EmpAppraisalForm(models.Model): extra_info = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE) year = models.DateField(max_length=6, null=True, blank=True) - appraisal_form = models.FileField( - upload_to='Hr2/appraisal_form', null=True, default=" ") + appraisal_form = models.FileField(upload_to='Hr2/appraisal_form', null=True, default=" ") def __str__(self): return self.extra_info.user.first_name - class WorkAssignemnt(models.Model): extra_info = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE) start_date = models.DateField(max_length=6, null=True, blank=True) @@ -148,28 +164,28 @@ class LTCform(models.Model): id = models.AutoField(primary_key=True) employeeId = models.IntegerField() name = models.CharField(max_length=100, null=True) - blockYear = models.TextField() # + blockYear = models.TextField() pfNo = models.IntegerField() basicPaySalary = models.IntegerField(null=True) designation = models.CharField(max_length=50) departmentInfo = models.CharField(max_length=50) - leaveRequired = models.BooleanField(default=False,null=True) # + leaveRequired = models.BooleanField(default=False, null=True) leaveStartDate = models.DateField(null=True, blank=True) leaveEndDate = models.DateField(null=True, blank=True) - dateOfDepartureForFamily = models.DateField(null=True, blank=True) # - natureOfLeave = models.TextField(null=True,blank=True) - purposeOfLeave = models.TextField(null=True,blank=True) + dateOfDepartureForFamily = models.DateField(null=True, blank=True) + natureOfLeave = models.TextField(null=True, blank=True) + purposeOfLeave = models.TextField(null=True, blank=True) hometownOrNot = models.BooleanField(default=False) - placeOfVisit = models.TextField(max_length=100, null=True, blank=True) + placeOfVisit = models.TextField(max_length=100, null=True, blank=True) addressDuringLeave = models.TextField(null=True) - modeofTravel = models.TextField(max_length=10, null=True,blank=True) # - detailsOfFamilyMembersAlreadyDone = models.JSONField(null=True,blank=True) - detailsOfFamilyMembersAboutToAvail = models.JSONField(max_length=100, null=True,blank=True) - detailsOfDependents = models.JSONField(blank=True,null=True) + modeofTravel = models.TextField(max_length=10, null=True, blank=True) + detailsOfFamilyMembersAlreadyDone = models.JSONField(null=True, blank=True) + detailsOfFamilyMembersAboutToAvail = models.JSONField(max_length=100, null=True, blank=True) + detailsOfDependents = models.JSONField(blank=True, null=True) amountOfAdvanceRequired = models.IntegerField(null=True, blank=True) - certifiedThatFamilyDependents = models.BooleanField(blank=True,null=True) - certifiedThatAdvanceTakenOn = models.DateField(null=True, blank=True) - adjustedMonth = models.TextField(max_length=50, null=True,blank=True) + certifiedThatFamilyDependents = models.BooleanField(blank=True, null=True) + certifiedThatAdvanceTakenOn = models.DateField(null=True, blank=True) + adjustedMonth = models.TextField(max_length=50, null=True, blank=True) submissionDate = models.DateField(null=True) phoneNumberForContact = models.BigIntegerField() approved = models.BooleanField(null=True) @@ -177,63 +193,206 @@ class LTCform(models.Model): created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='LTC_created_by') approved_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='LTC_approved_by') - - class CPDAAdvanceform(models.Model): id = models.AutoField(primary_key=True) employeeId = models.IntegerField(null=True) - name = models.CharField(max_length=40,null=True) - designation = models.CharField(max_length=40,null=True) + name = models.CharField(max_length=40, null=True) + designation = models.CharField(max_length=40, null=True) pfNo = models.IntegerField(null=True) purpose = models.TextField(max_length=40, null=True) amountRequired = models.IntegerField(null=True) - advanceDueAdjustment = models.DecimalField(max_digits=10, decimal_places=2, null=True,blank=True) - + advanceDueAdjustment = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) submissionDate = models.DateField(blank=True, null=True) - balanceAvailable = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True) advanceAmountPDA = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True) amountCheckedInPDA = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) - approved = models.BooleanField(null=True) approvedDate = models.DateField(auto_now_add=True, null=True) created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='CPDA_created_by') - approved_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='CPDA_approved_by') + approved_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name='CPDA_approved_by') -class LeaveForm(models.Model): +class CPDAReimbursementform(models.Model): id = models.AutoField(primary_key=True) employeeId = models.IntegerField(null=True) - name = models.CharField(max_length=40,null=True) - designation = models.CharField(max_length=40,null=True) + name = models.CharField(max_length=50) + designation = models.CharField(max_length=50) + pfNo = models.IntegerField() + advanceTaken = models.IntegerField() + purpose = models.TextField() + adjustmentSubmitted = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + balanceAvailable = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + advanceDueAdjustment = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + advanceAmountPDA = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + amountCheckedInPDA = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) submissionDate = models.DateField(blank=True, null=True) - pfNo = models.IntegerField(null=True) - departmentInfo = models.CharField(max_length=40,null=True) - natureOfLeave = models.TextField(max_length=40,null=True) + approved = models.BooleanField(null=True) + approvedDate = models.DateField(auto_now_add=True, null=True) + created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='CPDAR_created_by') + approved_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='CPDAR_approved_by') + +# Leave Application Table (old) +class LeaveForm(models.Model): + STATUS_CHOICES = [ + ('Accepted', 'Accepted'), + ('Pending', 'Pending'), + ('Rejected', 'Rejected'), + ] + Application_type_choices = [ + ('Online', 'Online'), + ('Offline', 'Offline'), + ] + + id = models.AutoField(primary_key=True) + employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name='leave_applications') + name = models.CharField(max_length=40, null=True) + designation = models.CharField(max_length=40, null=True) + submissionDate = models.DateField(default=date.today) + personalfileNo = models.CharField(max_length=50, null=True) + departmentInfo = models.CharField(max_length=40, null=True) leaveStartDate = models.DateField(blank=True, null=True) leaveEndDate = models.DateField(blank=True, null=True) - - purposeOfLeave = models.TextField(max_length=40,null=True) - addressDuringLeave = models.TextField(max_length=40, blank=True, null=True) - academicResponsibility = models.TextField(max_length=40, blank=True, null=True) - addministrativeResponsibiltyAssigned = models.TextField(max_length=40,null=True) + Noof_CasualLeave = models.IntegerField(default=0) + Noof_specialCasualLeave = models.IntegerField(default=0) + Noof_earnedLeave = models.IntegerField(default=0) + Noof_commutedLeave = models.IntegerField(default=0) + Noof_restrictedHoliday = models.IntegerField(default=0) + Noof_vacationLeave = models.IntegerField(default=0) + + Noof_maternityLeave = models.IntegerField(default=0) + Noof_childCareLeave = models.IntegerField(default=0) + Noof_paternityLeave = models.IntegerField(default=0) + Noof_halfPayLeave = models.IntegerField(default=0) + + LeavingStation = models.BooleanField(default=False) + StationLeave_startdate = models.DateField(blank=True, null=True) + StationLeave_enddate = models.DateField(blank=True, null=True) + Address_During_StationLeave = models.TextField(null=True, blank=True) + Purpose_of_leave = models.TextField(null=True, blank=True) + + AcademicResponsibility_user = models.ForeignKey( + Employee, + on_delete=models.SET_NULL, + null=True, + related_name='academic_responsibility_user' + ) + AcademicResponsibility_designation = models.ForeignKey(Designation, on_delete=models.CASCADE, null=True, related_name='leave_academic_responsibility_designation') + AcademicResponsibility_status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='Pending') + + AdministrativeResponsibility_user = models.ForeignKey( + Employee, + on_delete=models.SET_NULL, + null=True, + related_name='administrative_responsibility_user' + ) + AdministrativeResponsibility_designation = models.ForeignKey(Designation, on_delete=models.CASCADE, null=True, related_name='leave_administrative_responsibility_designation') + AdministrativeResponsibility_status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='Pending') + + Remarks = models.TextField(null=True, blank=True) - approved = models.BooleanField(null=True) approvedDate = models.DateField(auto_now_add=True, null=True) - created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='Leave_created_by') - approved_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='Leave_approved_by') + approved_by = models.ForeignKey(Employee, on_delete=models.CASCADE, null=True, related_name='leave_approved_by') + approved_by_designation = models.ForeignKey(Designation, on_delete=models.CASCADE, null=True, related_name='leave_approved_by_designation') + + first_recieved_by = models.ForeignKey(Employee, on_delete=models.CASCADE, null=True, related_name='leave_first_recieved_by') + first_recieved_designation = models.ForeignKey(Designation, on_delete=models.CASCADE, null=True, related_name='leave_first_recieved_designation') + + status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='Pending') + attached_pdf = models.BinaryField(null=True, blank=True) + attached_pdf_name = models.CharField(max_length=100, null=True, blank=True) + file_id = models.IntegerField(null=True, blank=True) + application_type = models.CharField(max_length=10, choices=Application_type_choices, default='Online') + + def __str__(self): + return f"Leave Application {self.id} - {self.employee.id.username}" + +class LeaveClaim(models.Model): + STATUS_CHOICES = [ + ('Accepted', 'Accepted'), + ('Pending', 'Pending'), + ('Rejected', 'Rejected'), + ] + APPLICATION_TYPE_CHOICES = [ + ('Online', 'Online'), + ('Offline', 'Offline'), + ] -class LeaveBalance(models.Model): id = models.AutoField(primary_key=True) - employeeId = models.OneToOneField(ExtraInfo, on_delete=models.CASCADE) - casualLeave = models.IntegerField(default=0) - specialCasualLeave = models.IntegerField(default=0) - earnedLeave = models.IntegerField(default=0) - commutedLeave = models.IntegerField(default=0) - restrictedHoliday = models.IntegerField(default=0) - stationLeave = models.IntegerField(default=0) - vacationLeave = models.IntegerField(default=0) + leave_form = models.ForeignKey(LeaveForm, on_delete=models.CASCADE, related_name='leave_claims') + claim_date = models.DateField(default=date.today) + + leaveStartDate = models.DateField(blank=True, null=True) + leaveEndDate = models.DateField(blank=True, null=True) + + Noof_CasualLeave = models.IntegerField(default=0) + Noof_specialCasualLeave = models.IntegerField(default=0) + Noof_earnedLeave = models.IntegerField(default=0) + Noof_commutedLeave = models.IntegerField(default=0) + Noof_restrictedHoliday = models.IntegerField(default=0) + Noof_vacationLeave = models.IntegerField(default=0) + Noof_maternityLeave = models.IntegerField(default=0) + Noof_childCareLeave = models.IntegerField(default=0) + Noof_paternityLeave = models.IntegerField(default=0) + Noof_halfPayLeave = models.IntegerField(default=0) + + remarks = models.TextField(null=True, blank=True) + + approvedDate = models.DateField(auto_now_add=True, null=True) + approved_by = models.ForeignKey( + Employee, + on_delete=models.CASCADE, + null=True, + related_name='leave_claim_approved_by' + ) + approved_by_designation = models.ForeignKey( + Designation, + on_delete=models.CASCADE, + null=True, + related_name='leave_claim_approved_by_designation' + ) + + status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='Pending') + attached_pdf = models.BinaryField(null=True, blank=True) + attached_pdf_name = models.CharField(max_length=100, null=True, blank=True) + file_id = models.IntegerField(null=True, blank=True) + application_type = models.CharField(max_length=10, choices=APPLICATION_TYPE_CHOICES, default='Online') + + def __str__(self): + return f"Leave Claim {self.id} for Form {self.leave_form.id}" + + class Meta: + verbose_name = "Leave Claim" + verbose_name_plural = "Leave Claims" + +class LeaveBalance(models.Model): + empid = models.OneToOneField(Employee, on_delete=models.CASCADE, related_name='leave_balance', primary_key=True) + casual_leave_taken = models.IntegerField(default=0) + special_casual_leave_taken = models.IntegerField(default=0) + earned_leave_taken = models.IntegerField(default=0) + half_pay_leave_taken = models.IntegerField(default=0) + maternity_leave_taken = models.IntegerField(default=0) + child_care_leave_taken = models.IntegerField(default=0) + paternity_leave_taken = models.IntegerField(default=0) + leave_encashment_taken = models.IntegerField(default=0) + restricted_holiday_taken = models.IntegerField(default=0) + + def __str__(self): + return f"Leave Balance for {self.empid.id.username}" + +class LeavePerYear(models.Model): + empid = models.OneToOneField(Employee, on_delete=models.CASCADE, related_name='yearly_leave', primary_key=True) + casual_leave = models.IntegerField(default=8) + special_casual_leave = models.IntegerField(default=15) + earned_leave = models.IntegerField(default=15) + half_pay_leave = models.IntegerField(default=15) + maternity_leave = models.IntegerField(default=180) + child_care_leave = models.IntegerField(default=730) + paternity_leave = models.IntegerField(default=15) + leave_encashment = models.IntegerField(default=60) + restricted_holiday = models.IntegerField(default=2) + def __str__(self): + return f"Yearly Leave Allotment for {self.empid.id.username}" class Appraisalform(models.Model): id = models.AutoField(primary_key=True) @@ -264,33 +423,565 @@ class Appraisalform(models.Model): otherContribution = models.TextField(max_length=40, null=True) performanceComments = models.TextField(max_length=100, null=True) submissionDate = models.DateField(max_length=6, null=True) - approved = models.BooleanField(null=True) approvedDate = models.DateField(auto_now_add=True, null=True) created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='Appraisal_created_by') approved_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='Appraisal_approved_by') -class CPDAReimbursementform(models.Model): - id = models.AutoField(primary_key=True) - employeeId = models.IntegerField(null=True) - name = models.CharField(max_length=50) - designation = models.CharField(max_length=50) - pfNo = models.IntegerField() - advanceTaken = models.IntegerField() - purpose = models.TextField() - adjustmentSubmitted = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) - balanceAvailable = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) - advanceDueAdjustment = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) - advanceAmountPDA = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) - amountCheckedInPDA = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) - submissionDate = models.DateField(auto_now_add=True) - approved = models.BooleanField(null=True) - approvedDate = models.DateField(auto_now_add=True, null=True) - created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='CPDAR_created_by') - approved_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='CPDAR_approved_by') - +# ==================== NEW MODELS FOR REST API (DO NOT OVERLAP) ==================== + +class EmployeeCategory(models.Model): + CATEGORY_TYPE = [ + ('TEACHING', 'Teaching'), + ('NON_TEACHING', 'Non-Teaching'), + ] + name = models.CharField(max_length=100) + category_type = models.CharField(max_length=20, choices=CATEGORY_TYPE) + pay_level = models.CharField(max_length=20, blank=True) + is_active = models.BooleanField(default=True) + + def __str__(self): + return self.name + +class EmployeeDetailsExtended(models.Model): + MARITAL_STATUS = [ + ('SINGLE', 'Single'), + ('MARRIED', 'Married'), + ('WIDOWED', 'Widowed'), + ('DIVORCED', 'Divorced'), + ] + EMPLOYEE_STATUS = [ + ('ACTIVE', 'Active'), + ('ON_LEAVE', 'On Leave'), + ('DEPUTATION', 'On Deputation'), + ('SUSPENDED', 'Suspended'), + ('RETIRED', 'Retired'), + ('RESIGNED', 'Resigned'), + ('TERMINATED', 'Terminated'), + ] + + extra_info = models.OneToOneField(ExtraInfo, on_delete=models.CASCADE, related_name='employee_details_extended') + category = models.ForeignKey(EmployeeCategory, on_delete=models.PROTECT) + + father_name = models.CharField(max_length=100, blank=True) + mother_name = models.CharField(max_length=100, blank=True) + spouse_name = models.CharField(max_length=100, blank=True) + marital_status = models.CharField(max_length=20, choices=MARITAL_STATUS, blank=True) + + pan_number = models.CharField(max_length=15, blank=True) + aadhar_number = models.CharField(max_length=15, blank=True) + passport_number = models.CharField(max_length=20, blank=True) + + date_of_joining = models.DateField(null=True, blank=True) + date_of_superannuation = models.DateField(null=True, blank=True) + appointment_type = models.CharField(max_length=50, blank=True) + + employee_status = models.CharField(max_length=20, choices=EMPLOYEE_STATUS, default='ACTIVE') + + bank_name = models.CharField(max_length=100, blank=True) + bank_account = models.CharField(max_length=30, blank=True) + ifsc_code = models.CharField(max_length=15, blank=True) + + emergency_contact_name = models.CharField(max_length=100, blank=True) + emergency_contact_phone = models.CharField(max_length=15, blank=True) + emergency_contact_relation = models.CharField(max_length=50, blank=True) + + def __str__(self): + return f"{self.extra_info.user.username} - EmployeeDetailsExtended" +class ServiceHistory(models.Model): + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='service_history') + designation = models.ForeignKey(Designation, on_delete=models.PROTECT) + department = models.ForeignKey(DepartmentInfo, on_delete=models.PROTECT) + from_date = models.DateField() + to_date = models.DateField(null=True, blank=True) + pay_scale = models.CharField(max_length=50, blank=True) + basic_pay = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) + remarks = models.TextField(blank=True) + class Meta: + ordering = ['-from_date'] +class QualificationType(models.Model): + name = models.CharField(max_length=100) + level = models.IntegerField() # 1=School, 2=UG, 3=PG, 4=Doctoral + def __str__(self): + return self.name + +class EducationalQualification(models.Model): + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='qualifications') + qualification_type = models.ForeignKey(QualificationType, on_delete=models.PROTECT) + degree = models.CharField(max_length=100) + specialization = models.CharField(max_length=200, blank=True) + institution = models.CharField(max_length=200) + university = models.CharField(max_length=200, blank=True) + year_of_passing = models.IntegerField() + division_grade = models.CharField(max_length=50, blank=True) + document = models.FileField(upload_to='hr/qualifications/', blank=True) + +class ProfessionalQualification(models.Model): + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='professional_qualifications') + title = models.CharField(max_length=200) + certifying_body = models.CharField(max_length=200) + date_obtained = models.DateField() + valid_until = models.DateField(null=True, blank=True) + document = models.FileField(upload_to='hr/professional/', blank=True) + +class PreviousExperience(models.Model): + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='previous_experiences') + organization = models.CharField(max_length=200) + designation = models.CharField(max_length=100) + from_date = models.DateField() + to_date = models.DateField() + experience_type = models.CharField(max_length=50) # Teaching, Industry, Research + description = models.TextField(blank=True) + document = models.FileField(upload_to='hr/experience/', blank=True) + +class LeaveType(models.Model): + name = models.CharField(max_length=50) # CL, EL, HPL, etc. + code = models.CharField(max_length=10, unique=True) + max_days_per_year = models.IntegerField(null=True, blank=True) + carry_forward = models.BooleanField(default=False) + max_carry_forward = models.IntegerField(null=True, blank=True) + is_active = models.BooleanField(default=True) + + def __str__(self): + return f"{self.name} ({self.code})" + +class EmployeeLeaveBalance(models.Model): + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='leave_balances_new') + leave_type = models.ForeignKey(LeaveType, on_delete=models.PROTECT) + year = models.IntegerField() + opening_balance = models.DecimalField(max_digits=5, decimal_places=1, default=0) + accrued = models.DecimalField(max_digits=5, decimal_places=1, default=0) + availed = models.DecimalField(max_digits=5, decimal_places=1, default=0) + current_balance = models.DecimalField(max_digits=5, decimal_places=1, default=0) + + class Meta: + unique_together = ['employee', 'leave_type', 'year'] + +class LeaveApplicationNew(models.Model): + STATUS_CHOICES = [ + ('PENDING', 'Pending'), + ('FORWARDED', 'Forwarded'), + ('APPROVED', 'Approved'), + ('REJECTED', 'Rejected'), + ('WITHDRAWN', 'Withdrawn'), + ('CANCELLED', 'Cancelled'), + ] + + LEAVE_TYPE_CHOICES = [ + ('Casual', 'Casual'), + ('Restricted', 'Restricted'), + ('Medical', 'Medical'), + ('Earned', 'Earned'), + ('Vacation', 'Vacation'), + ('Sabbatical', 'Sabbatical'), + ] + + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='leave_applications_new') + employee_name = models.CharField(max_length=100) + department = models.CharField(max_length=100) + designation = models.CharField(max_length=100) + + leave_type = models.CharField(max_length=30, choices=LEAVE_TYPE_CHOICES) + start_date = models.DateField() + end_date = models.DateField() + total_days = models.DecimalField(max_digits=5, decimal_places=1) + reason = models.TextField() + station_leave = models.CharField( + max_length=12, + choices=[('WITH', 'With Station Leave'), ('WITHOUT', 'Without Station Leave'), ('NOT_REQUIRED', 'Not Required')], + blank=True, + ) + is_half_day = models.BooleanField(default=False) + half_day_slot = models.CharField( + max_length=2, + choices=[('AM', 'AM'), ('PM', 'PM')], + blank=True, + ) + + contact_during_leave = models.CharField(max_length=15) + address_during_leave = models.TextField() + handover_to = models.CharField(max_length=100, blank=True) + handover_notes = models.TextField(blank=True) + nominee_status = models.CharField( + max_length=20, + choices=[('NOT_REQUIRED', 'Not Required'), ('PENDING', 'Pending'), ('ACCEPTED', 'Accepted'), ('DECLINED', 'Declined')], + default='NOT_REQUIRED', + ) + nominee_responded_at = models.DateTimeField(null=True, blank=True) + + medical_certificate = models.CharField(max_length=200, blank=True) + attachment_file = models.CharField(max_length=200, blank=True) + + applied_date = models.DateField(auto_now_add=True) + leave_balance_before = models.DecimalField(max_digits=5, decimal_places=1, null=True, blank=True) + leave_balance_after = models.DecimalField(max_digits=5, decimal_places=1, null=True, blank=True) + approval_status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING') + current_approver_role = models.CharField(max_length=50, blank=True) + remarks = models.TextField(blank=True) + + document_request_message = models.TextField(blank=True) + document_request_status = models.CharField( + max_length=20, + choices=[('NOT_REQUESTED', 'Not Requested'), ('REQUESTED', 'Requested'), ('SUBMITTED', 'Submitted')], + default='NOT_REQUESTED', + ) + document_requested_at = models.DateTimeField(null=True, blank=True) + document_submission = models.TextField(blank=True) + document_submitted_at = models.DateTimeField(null=True, blank=True) + + cancel_status = models.CharField( + max_length=20, + choices=[('NOT_REQUESTED', 'Not Requested'), ('REQUESTED', 'Requested'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected')], + default='NOT_REQUESTED', + ) + cancel_requested_at = models.DateTimeField(null=True, blank=True) + cancel_decided_at = models.DateTimeField(null=True, blank=True) + cancel_requested_by_role = models.CharField(max_length=50, blank=True) + cancel_current_approver_role = models.CharField(max_length=50, blank=True) + cancel_reason = models.TextField(blank=True) + cancel_decision_remarks = models.TextField(blank=True) + + extension_status = models.CharField( + max_length=20, + choices=[('NOT_REQUESTED', 'Not Requested'), ('REQUESTED', 'Requested'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected')], + default='NOT_REQUESTED', + ) + extension_requested_at = models.DateTimeField(null=True, blank=True) + extension_decided_at = models.DateTimeField(null=True, blank=True) + extension_requested_by_role = models.CharField(max_length=50, blank=True) + extension_current_approver_role = models.CharField(max_length=50, blank=True) + extension_reason = models.TextField(blank=True) + extension_new_end_date = models.DateField(null=True, blank=True) + extension_new_total_days = models.DecimalField(max_digits=5, decimal_places=1, null=True, blank=True) + extension_decision_remarks = models.TextField(blank=True) + + resumption_status = models.CharField( + max_length=20, + choices=[('NOT_REQUESTED', 'Not Requested'), ('SUBMITTED', 'Submitted'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected')], + default='NOT_REQUESTED', + ) + resumption_date = models.DateField(null=True, blank=True) + resumption_reason = models.TextField(blank=True) + resumption_submitted_at = models.DateTimeField(null=True, blank=True) + resumption_decided_at = models.DateTimeField(null=True, blank=True) + resumption_current_approver_role = models.CharField(max_length=50, blank=True) + resumption_decision_remarks = models.TextField(blank=True) + + def __str__(self): + return f"LeaveNew #{self.id} - {self.employee.user.username}" + +class AppraisalPeriod(models.Model): + name = models.CharField(max_length=100) + start_date = models.DateField() + end_date = models.DateField() + submission_deadline = models.DateField() + is_active = models.BooleanField(default=False) + + def __str__(self): + return self.name + +class PerformanceAppraisalNew(models.Model): + STATUS_CHOICES = [ + ('DRAFT', 'Draft'), + ('SUBMITTED', 'Submitted'), + ('REVIEWED', 'Reviewed'), + ('APPROVED', 'Approved'), + ('FINALIZED', 'Finalized'), + ] + + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='appraisals_new') + period = models.ForeignKey(AppraisalPeriod, on_delete=models.PROTECT) + + teaching_score = models.IntegerField(null=True, blank=True) + research_score = models.IntegerField(null=True, blank=True) + admin_score = models.IntegerField(null=True, blank=True) + extension_score = models.IntegerField(null=True, blank=True) + self_remarks = models.TextField(blank=True) + + reviewer_teaching_score = models.IntegerField(null=True, blank=True) + reviewer_research_score = models.IntegerField(null=True, blank=True) + reviewer_admin_score = models.IntegerField(null=True, blank=True) + reviewer_extension_score = models.IntegerField(null=True, blank=True) + reviewer = models.ForeignKey(ExtraInfo, on_delete=models.SET_NULL, null=True, blank=True, related_name='reviewed_performance_appraisals_new') + reviewer_remarks = models.TextField(blank=True) + + final_score = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) + final_grade = models.CharField(max_length=10, blank=True) + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='DRAFT') + + submitted_at = models.DateTimeField(null=True, blank=True) + finalized_at = models.DateTimeField(null=True, blank=True) + +class TrainingProgram(models.Model): + title = models.CharField(max_length=200) + description = models.TextField() + organizer = models.CharField(max_length=200) + venue = models.CharField(max_length=200) + start_date = models.DateField() + end_date = models.DateField() + max_participants = models.IntegerField(null=True, blank=True) + is_mandatory = models.BooleanField(default=False) + +class TrainingNomination(models.Model): + STATUS_CHOICES = [ + ('NOMINATED', 'Nominated'), + ('APPROVED', 'Approved'), + ('REJECTED', 'Rejected'), + ('ATTENDED', 'Attended'), + ('COMPLETED', 'Completed'), + ] + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='training_nominations') + program = models.ForeignKey(TrainingProgram, on_delete=models.CASCADE) + nominated_by = models.ForeignKey(ExtraInfo, on_delete=models.SET_NULL, null=True, related_name='training_nominations_made') + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='NOMINATED') + feedback = models.TextField(blank=True) + certificate = models.FileField(upload_to='hr/training/', blank=True) + +class PromotionApplication(models.Model): + STATUS_CHOICES = [ + ('SUBMITTED', 'Submitted'), + ('UNDER_REVIEW', 'Under Review'), + ('COMMITTEE_STAGE', 'At Committee'), + ('APPROVED', 'Approved'), + ('REJECTED', 'Rejected'), + ] + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='promotion_applications') + current_designation = models.ForeignKey(Designation, on_delete=models.PROTECT, related_name='current_for_promotions') + applied_designation = models.ForeignKey(Designation, on_delete=models.PROTECT, related_name='applied_promotions') + application_date = models.DateField() + eligibility_date = models.DateField() + api_score = models.IntegerField(null=True, blank=True) + documents = models.FileField(upload_to='hr/promotions/', blank=True) + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='SUBMITTED') + remarks = models.TextField(blank=True) + approved_date = models.DateField(null=True, blank=True) + effective_date = models.DateField(null=True, blank=True) + +class EmployeeAttendance(models.Model): + ATTENDANCE_STATUS = [ + ('PRESENT', 'Present'), + ('ABSENT', 'Absent'), + ('HALF_DAY', 'Half Day'), + ('ON_LEAVE', 'On Leave'), + ('ON_TOUR', 'On Tour'), + ('WORK_FROM_HOME', 'WFH'), + ] + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='attendance_records') + date = models.DateField() + status = models.CharField(max_length=20, choices=ATTENDANCE_STATUS) + in_time = models.TimeField(null=True, blank=True) + out_time = models.TimeField(null=True, blank=True) + remarks = models.TextField(blank=True) + + class Meta: + unique_together = ['employee', 'date'] + +class FacultyWorkload(models.Model): + faculty = models.ForeignKey(Faculty, on_delete=models.CASCADE, related_name='workloads') + semester = models.CharField(max_length=20) + year = models.IntegerField() + lecture_hours = models.IntegerField(default=0) + tutorial_hours = models.IntegerField(default=0) + lab_hours = models.IntegerField(default=0) + total_hours = models.IntegerField(default=0) + total_students = models.IntegerField(default=0) + phd_scholars = models.IntegerField(default=0) + + class Meta: + unique_together = ['faculty', 'semester', 'year'] + +class LTCApplicationNew(models.Model): + STATUS_CHOICES = [ + ('PENDING', 'Pending'), + ('FORWARDED', 'Forwarded'), + ('APPROVED', 'Approved'), + ('REJECTED', 'Rejected'), + ('WITHDRAWN', 'Withdrawn'), + ] + + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='ltc_applications_new') + employee_name = models.CharField(max_length=100) + department = models.CharField(max_length=100) + designation = models.CharField(max_length=100) + + ltc_block_year = models.IntegerField() + travel_start_date = models.DateField() + travel_end_date = models.DateField() + destination = models.CharField(max_length=200) + purpose_of_travel = models.TextField() + + family_members = models.TextField(blank=True) + relationship_details = models.TextField(blank=True) + + travel_mode = models.CharField(max_length=50) + ticket_number = models.CharField(max_length=100, blank=True) + ticket_cost = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + accommodation_cost = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + other_expenses = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + total_amount_claimed = models.DecimalField(max_digits=10, decimal_places=2) + + tickets_upload = models.CharField(max_length=200, blank=True) + bills_upload = models.CharField(max_length=200, blank=True) + + previous_ltc_used = models.BooleanField(default=False) + last_ltc_date = models.DateField(null=True, blank=True) + + applied_date = models.DateField(auto_now_add=True) + verified_by_hr = models.BooleanField(default=False) + approval_status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING') + accountant_status = models.CharField(max_length=20, blank=True) + remarks = models.TextField(blank=True) + + def __str__(self): + return f"LTCNew #{self.id} - {self.employee.user.username}" + +class CPDAAdvanceNew(models.Model): + STATUS_CHOICES = [ + ('PENDING', 'Pending'), + ('FORWARDED', 'Forwarded'), + ('APPROVED', 'Approved'), + ('REJECTED', 'Rejected'), + ('WITHDRAWN', 'Withdrawn'), + ] + + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='cpda_advances_new') + employee_name = models.CharField(max_length=100) + department = models.CharField(max_length=100) + designation = models.CharField(max_length=100) + + event_name = models.CharField(max_length=200) + event_type = models.CharField(max_length=50) + organized_by = models.CharField(max_length=200, blank=True) + venue = models.CharField(max_length=200, blank=True) + start_date = models.DateField() + end_date = models.DateField() + + registration_fee = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + travel_expense = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + accommodation_expense = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + other_expenses = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + total_amount = models.DecimalField(max_digits=10, decimal_places=2) + + purpose_of_attending = models.TextField() + benefits_to_institution = models.TextField() + + invitation_letter = models.CharField(max_length=200, blank=True) + receipts = models.CharField(max_length=200, blank=True) + certificates = models.CharField(max_length=200, blank=True) + + applied_date = models.DateField(auto_now_add=True) + verified_by_hr = models.BooleanField(default=False) + approval_status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING') + accountant_processing_status = models.CharField(max_length=30, blank=True) + remarks = models.TextField(blank=True) + + def __str__(self): + return f"CPDAAdvanceNew #{self.id} - {self.employee.user.username}" + +class CPDAReimbursementNew(models.Model): + STATUS_CHOICES = [ + ('PENDING', 'Pending'), + ('FORWARDED', 'Forwarded'), + ('APPROVED', 'Approved'), + ('REJECTED', 'Rejected'), + ] + + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='cpda_reimbursements_new') + employee_name = models.CharField(max_length=100) + department = models.CharField(max_length=100) + designation = models.CharField(max_length=100) + + event_name = models.CharField(max_length=200) + event_type = models.CharField(max_length=50) + organized_by = models.CharField(max_length=200, blank=True) + venue = models.CharField(max_length=200, blank=True) + start_date = models.DateField() + end_date = models.DateField() + + registration_fee = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + travel_expense = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + accommodation_expense = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + other_expenses = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + total_amount = models.DecimalField(max_digits=10, decimal_places=2) + + purpose_of_attending = models.TextField() + benefits_to_institution = models.TextField() + + invitation_letter = models.CharField(max_length=200, blank=True) + receipts = models.CharField(max_length=200, blank=True) + certificates = models.CharField(max_length=200, blank=True) + + applied_date = models.DateField(auto_now_add=True) + verified_by_hr = models.BooleanField(default=False) + approval_status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING') + accountant_processing_status = models.CharField(max_length=30, blank=True) + remarks = models.TextField(blank=True) + + def __str__(self): + return f"CPDAReimbursementNew #{self.id} - {self.employee.user.username}" + +class AppraisalFormNew(models.Model): + STATUS_CHOICES = [ + ('PENDING', 'Pending'), + ('REVIEWED', 'Reviewed'), + ('APPROVED', 'Approved'), + ] + + employee = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE, related_name='appraisal_forms_new') + employee_name = models.CharField(max_length=100) + department = models.CharField(max_length=100) + designation = models.CharField(max_length=100) + appraisal_year = models.CharField(max_length=20) + + self_summary = models.TextField() + key_responsibilities = models.TextField() + achievements = models.TextField() + challenges_faced = models.TextField(blank=True) + + teaching_performance = models.TextField(blank=True) + research_work = models.TextField(blank=True) + publications = models.TextField(blank=True) + projects_handled = models.TextField(blank=True) + administrative_contributions = models.TextField(blank=True) + + trainings_attended = models.TextField(blank=True) + certifications = models.TextField(blank=True) + workshops = models.TextField(blank=True) + + goals_achieved = models.TextField() + future_goals = models.TextField() + + supporting_documents = models.CharField(max_length=200, blank=True) + + reviewer_id = models.CharField(max_length=50, blank=True) + reviewer_comments = models.TextField(blank=True) + rating = models.CharField(max_length=20, blank=True) + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING') + remarks = models.TextField(blank=True) + + assigned_reviewer_role = models.CharField(max_length=20, blank=True) + assigned_reviewer = models.ForeignKey( + ExtraInfo, + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name='assigned_appraisals_new', + ) + assigned_by = models.ForeignKey( + ExtraInfo, + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name='appraisal_assignments_made', + ) + assigned_at = models.DateTimeField(null=True, blank=True) + + submitted_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"AppraisalNew #{self.id} - {self.employee.user.username}" \ No newline at end of file diff --git a/FusionIIIT/applications/hr2/normal.py b/FusionIIIT/applications/hr2/normal.py deleted file mode 100644 index 0e344ff51..000000000 --- a/FusionIIIT/applications/hr2/normal.py +++ /dev/null @@ -1,36 +0,0 @@ -'block_year': ['232'], - 'pf_no': ['222'], - 'basic_pay_salary': ['2322'], - 'name': ['dsds'], - 'designation':['dsdsd'], - 'department_info': ['ds'], - 'leave_availability': ['True', 'True'], - 'leave_start_date': ['2024-02-22'], - 'leave_end_date': ['2024-02-22'], - 'date_of_leave_for_family': ['2024-02-22'], - 'nature_of_leave': ['dsds'], - 'purpose_of_leave': ['dsdsd'], - 'hometown_or_not': ['True'], - 'place_of_visit': [''], - 'address_during_leave': ['full street address'], - 'details_of_family_members_already_done': ['sds', 'dsd', 'dsd'], - 'info_1_1': ['1'], 'info_1_2': ['dsds'], 'info_1_3': ['12'], - 'info_2_1': ['2'], 'info_2_2': ['sds'], 'info_2_3': ['121'], - 'info_3_1': ['3'], 'info_3_2': ['dsds'], 'info_3_3': ['21'], - 'info_4_1': [''], 'info_4_2': [''], 'info_4_3': [''], - 'info_5_1': [''], 'info_5_2': [''], 'info_5_3': [''], - 'info_6_1': [''], 'info_6_2': [''], 'info_6_3': [''], - - 'd_info_1_1': ['1'], 'd_info_1_2': ['sds'], 'd_info_1_3': ['21'], 'd_info_1_4': ['sdd'], - - 'd_info_2_1': ['2'], 'd_info_2_2': ['dsd'], 'd_info_2_3': ['23'], 'd_info_2_4': ['sds'], - 'd_info_3_1': ['3'], 'd_info_3_2': ['sd'], 'd_info_3_3': ['21'], 'd_info_3_4': ['dds'], - 'd_info_4_1': [''], 'd_info_4_2': [''], 'd_info_4_3': [''], 'd_info_4_4': [''], - 'd_info_5_1': [''], 'd_info_5_2': [''], 'd_info_5_3': [''], 'd_info_5_4': [''], - 'd_info_6_1': [''], 'd_info_6_2': [''], 'd_info_6_3': [''], 'd_info_6_4': [''], - 'amount_of_advance_required': ['211'], - 'certified_family_dependents': ['dqwd'], - 'certified_advance': ['dqwd'], - 'adjusted_month': ['qwdwd'], - 'date': ['2024-02-22'], - 'phone_number_for_contact': ['2312123'] \ No newline at end of file diff --git a/FusionIIIT/applications/hr2/selectors.py b/FusionIIIT/applications/hr2/selectors.py new file mode 100644 index 000000000..a75e84ff5 --- /dev/null +++ b/FusionIIIT/applications/hr2/selectors.py @@ -0,0 +1,399 @@ +from datetime import date + +from django.db.models import Q +from django.http import Http404 + +from applications.globals.models import ExtraInfo, HoldsDesignation +from .models import ( + AppraisalFormNew, + AppraisalPeriod, + CPDAAdvanceNew, + CPDAReimbursementNew, + EmployeeAttendance, + EmployeeLeaveBalance, + FacultyWorkload, + LeaveApplicationNew, + LeaveType, + LTCApplicationNew, + PerformanceAppraisalNew, + PromotionApplication, + TrainingNomination, + TrainingProgram, +) + +# ==================== EMPLOYEE SELECTORS ==================== + +def get_employee_by_id(employee_id): + return ExtraInfo.objects.select_related('user', 'department').get(id=employee_id) + + +def get_employee_by_id_optional(employee_id): + if not employee_id: + return None + return ExtraInfo.objects.filter(id=employee_id).select_related('user', 'department').first() + + +def get_employee_by_id_or_404(employee_id): + try: + return get_employee_by_id(employee_id) + except ExtraInfo.DoesNotExist as exc: + raise Http404(f"Employee not found: {employee_id}") from exc + +def get_all_employees(employee_type=None, department_id=None): + qs = ExtraInfo.objects.select_related('user', 'department') + if employee_type: + qs = qs.filter(user_type=employee_type) + if department_id: + qs = qs.filter(department_id=department_id) + return qs + +def get_employee_current_designation(employee_extra_info): + held = HoldsDesignation.objects.filter(working=employee_extra_info).order_by('-held_at').first() + return held.designation if held else None + + +def get_employee_current_designation_for_user(user): + held = HoldsDesignation.objects.filter(working=user).order_by('-held_at').first() + return held.designation if held else None + + +def get_employee_for_user(user): + return ExtraInfo.objects.filter(user=user).select_related('user', 'department').first() + + +def get_leave_application_by_id_or_404(pk): + try: + return LeaveApplicationNew.objects.get(pk=pk) + except LeaveApplicationNew.DoesNotExist as exc: + raise Http404("Leave application not found") from exc + + +def get_ltc_application_by_id_or_404(pk): + try: + return LTCApplicationNew.objects.get(pk=pk) + except LTCApplicationNew.DoesNotExist as exc: + raise Http404("LTC application not found") from exc + + +def get_cpda_advance_by_id_or_404(pk): + try: + return CPDAAdvanceNew.objects.get(pk=pk) + except CPDAAdvanceNew.DoesNotExist as exc: + raise Http404("CPDA advance not found") from exc + + +def get_cpda_reimbursement_by_id_or_404(pk): + try: + return CPDAReimbursementNew.objects.get(pk=pk) + except CPDAReimbursementNew.DoesNotExist as exc: + raise Http404("CPDA reimbursement not found") from exc + + +def get_appraisal_form_by_id_or_404(pk): + try: + return AppraisalFormNew.objects.get(pk=pk) + except AppraisalFormNew.DoesNotExist as exc: + raise Http404("Appraisal form not found") from exc + +# ==================== LEAVE SELECTORS ==================== + +def get_leave_balance_for_employee(employee_extra_info, leave_type, year=None): + if year is None: + year = date.today().year + return EmployeeLeaveBalance.objects.select_related('leave_type').get( + employee=employee_extra_info, + leave_type=leave_type, + year=year + ) + + +def get_leave_type_by_name(leave_type_name): + return LeaveType.objects.filter(name__iexact=leave_type_name).first() + + +def get_leave_balances_for_employee(employee): + return ( + EmployeeLeaveBalance.objects.filter(employee=employee) + .select_related('leave_type') + .order_by('leave_type_id', '-year', '-id') + ) + + +def get_latest_leave_balances_for_employee(employee): + balances = [] + seen_leave_types = set() + for balance in get_leave_balances_for_employee(employee): + if balance.leave_type_id in seen_leave_types: + continue + seen_leave_types.add(balance.leave_type_id) + balances.append(balance) + return balances + + +def get_leave_balance_for_employee_year(employee, leave_type, year): + return EmployeeLeaveBalance.objects.filter( + employee=employee, + leave_type=leave_type, + year=year, + ).first() + + +def get_latest_leave_balance_for_employee(employee, leave_type): + return EmployeeLeaveBalance.objects.filter( + employee=employee, + leave_type=leave_type, + ).order_by('-year').first() + + +def has_overlapping_leave(employee, start_date, end_date, exclude_id=None): + qs = LeaveApplicationNew.objects.filter( + employee=employee, + approval_status__in=['PENDING', 'FORWARDED', 'APPROVED'], + start_date__lte=end_date, + end_date__gte=start_date, + ) + if exclude_id is not None: + qs = qs.exclude(id=exclude_id) + return qs + + +def get_leave_applications_for_nominee(employee_id): + return LeaveApplicationNew.objects.filter( + handover_to=employee_id, + nominee_status='PENDING', + ).order_by('-applied_date') + +def get_leave_applications(employee_extra_info, status=None, from_date=None, to_date=None): + qs = LeaveApplicationNew.objects.filter(employee=employee_extra_info) + if status: + qs = qs.filter(approval_status=status) + if from_date: + qs = qs.filter(start_date__gte=from_date) + if to_date: + qs = qs.filter(end_date__lte=to_date) + return qs.order_by('-applied_date') + + +def get_leave_applications_for_role_view(user, role_flags): + if role_flags.get('is_hr_staff'): + return LeaveApplicationNew.objects.all() + if role_flags.get('is_director'): + return LeaveApplicationNew.objects.filter( + Q( + approval_status='FORWARDED', + current_approver_role__iexact='Director', + ) + | Q(employee=user.extrainfo) + | Q( + cancel_status='REQUESTED', + cancel_current_approver_role__iexact='Director', + ) + | Q( + extension_status='REQUESTED', + extension_current_approver_role__iexact='Director', + ) + ) + if role_flags.get('is_registrar'): + return LeaveApplicationNew.objects.filter( + Q( + approval_status='FORWARDED', + current_approver_role__iexact='Registrar', + ) + | Q(employee=user.extrainfo) + | Q( + cancel_status='REQUESTED', + cancel_current_approver_role__iexact='Registrar', + ) + | Q( + extension_status='REQUESTED', + extension_current_approver_role__iexact='Registrar', + ) + ) + if role_flags.get('is_hod'): + return LeaveApplicationNew.objects.filter( + department=user.extrainfo.department.name + ) + return get_leave_applications(user.extrainfo) + +def get_pending_responsibility_leaves(employee_extra_info, responsibility_type='academic'): + if responsibility_type == 'academic': + return LeaveApplicationNew.objects.filter(employee=employee_extra_info, approval_status='PENDING') + return LeaveApplicationNew.objects.filter(employee=employee_extra_info, approval_status='PENDING') + + +def get_leave_applications_for_employee_and_status(employee_extra_info, status=None): + qs = LeaveApplicationNew.objects.filter(employee=employee_extra_info) + if status: + qs = qs.filter(approval_status=status) + return qs.order_by('-applied_date') + +# ==================== ATTENDANCE SELECTORS ==================== + +def get_attendance_for_employee(employee_extra_info, from_date=None, to_date=None): + qs = EmployeeAttendance.objects.filter(employee=employee_extra_info) + if from_date: + qs = qs.filter(date__gte=from_date) + if to_date: + qs = qs.filter(date__lte=to_date) + return qs.order_by('date') + +# ==================== APPRAISAL SELECTORS ==================== + +def get_appraisal_periods(is_active=None): + qs = AppraisalPeriod.objects.all() + if is_active is not None: + qs = qs.filter(is_active=is_active) + return qs + + +def get_appraisal_forms_for_role_view(user, role_flags): + if role_flags.get('is_hr_staff'): + return AppraisalFormNew.objects.all().order_by('-submitted_at') + if role_flags.get('is_director'): + return AppraisalFormNew.objects.filter( + assigned_reviewer_role__iexact='DIRECTOR', + ).filter( + Q(assigned_reviewer__isnull=True) + | Q(assigned_reviewer=user.extrainfo) + ).filter( + status__in=['PENDING', 'REVIEWED'] + ).order_by('-submitted_at') + if role_flags.get('is_hod'): + return AppraisalFormNew.objects.filter( + assigned_reviewer_role__iexact='HOD', + department=user.extrainfo.department.name, + ).filter( + Q(assigned_reviewer__isnull=True) + | Q(assigned_reviewer=user.extrainfo) + ).filter( + status='PENDING' + ).order_by('-submitted_at') + return get_appraisal_forms(user.extrainfo) + +def get_appraisals_for_employee(employee_extra_info, period_id=None): + qs = PerformanceAppraisalNew.objects.filter(employee=employee_extra_info).select_related('period') + if period_id: + qs = qs.filter(period_id=period_id) + return qs + +# ==================== TRAINING SELECTORS ==================== + +def get_available_training_programs(): + today = date.today() + return TrainingProgram.objects.filter(start_date__gte=today) + +def get_nominations_for_employee(employee_extra_info): + return TrainingNomination.objects.filter(employee=employee_extra_info).select_related('program') + +# ==================== PROMOTION SELECTORS ==================== + +def get_promotion_applications(employee_extra_info=None): + qs = PromotionApplication.objects.select_related('employee', 'current_designation', 'applied_designation') + if employee_extra_info: + qs = qs.filter(employee=employee_extra_info) + return qs + +# ==================== FACULTY WORKLOAD SELECTORS ==================== + +def get_faculty_workload(faculty_extra_info, semester=None, year=None): + qs = FacultyWorkload.objects.filter(faculty=faculty_extra_info.faculty_profile) + if semester: + qs = qs.filter(semester=semester) + if year: + qs = qs.filter(year=year) + return qs + +# LTC +def get_ltc_applications(employee_extra_info, status=None): + qs = LTCApplicationNew.objects.filter(employee=employee_extra_info) + if status: + qs = qs.filter(approval_status=status) + return qs.order_by('-applied_date') + + +def get_ltc_applications_for_role_view(user, role_flags): + if role_flags.get('is_hr_staff'): + return LTCApplicationNew.objects.filter(approval_status__in=['PENDING', 'FORWARDED']) + if role_flags.get('is_accountant'): + return LTCApplicationNew.objects.filter( + approval_status='FORWARDED', + accountant_status__iexact='PENDING', + ) + return get_ltc_applications(user.extrainfo) + +# CPDA Advance +def get_cpda_advances(employee_extra_info, status=None): + qs = CPDAAdvanceNew.objects.filter(employee=employee_extra_info) + if status: + qs = qs.filter(approval_status=status) + return qs.order_by('-applied_date') + + +def get_cpda_advances_for_role_view(user, role_flags): + if role_flags.get('is_director'): + return CPDAAdvanceNew.objects.filter( + approval_status='FORWARDED', + accountant_processing_status__iexact='DIRECTOR_REVIEW', + ) + if role_flags.get('is_hr_staff'): + return CPDAAdvanceNew.objects.filter(approval_status='PENDING') + if role_flags.get('is_accountant'): + return CPDAAdvanceNew.objects.filter( + approval_status='FORWARDED', + accountant_processing_status__in=['PENDING'], + ) + return get_cpda_advances(user.extrainfo) + +# CPDA Reimbursement +def get_cpda_reimbursements(employee_extra_info, status=None): + qs = CPDAReimbursementNew.objects.filter(employee=employee_extra_info) + if status: + qs = qs.filter(approval_status=status) + return qs.order_by('-applied_date') + +# Appraisal Form +def get_appraisal_forms(employee_extra_info, status=None): + qs = AppraisalFormNew.objects.filter(employee=employee_extra_info) + if status: + qs = qs.filter(status=status) + return qs.order_by('-submitted_at') + + +def get_reviewer_by_id(reviewer_id): + if not reviewer_id: + return None + return ExtraInfo.objects.filter(id=reviewer_id).first() + + +def get_role_flags(user): + return { + 'is_hr_staff': HoldsDesignation.objects.filter( + working=user, + designation__name__icontains='hr', + ).exists() or ( + hasattr(user, 'extrainfo') + and user.extrainfo.user_type == 'staff' + and user.extrainfo.department + and user.extrainfo.department.name == 'HR' + ), + 'is_hod': HoldsDesignation.objects.filter( + working=user, + designation__name__icontains='hod', + ).exists(), + 'is_director': HoldsDesignation.objects.filter( + working=user, + designation__name__icontains='director', + ).exists(), + 'is_registrar': HoldsDesignation.objects.filter( + working=user, + designation__name__icontains='registrar', + ).exists(), + 'is_accountant': HoldsDesignation.objects.filter( + working=user, + designation__name__icontains='accountant', + ).exists(), + 'is_hr_admin': HoldsDesignation.objects.filter( + working=user, + designation__name__iregex=r'hr admin|hr administrator', + ).exists(), + } \ No newline at end of file diff --git a/FusionIIIT/applications/hr2/serializers.py b/FusionIIIT/applications/hr2/serializers.py deleted file mode 100644 index b28cdcc45..000000000 --- a/FusionIIIT/applications/hr2/serializers.py +++ /dev/null @@ -1,42 +0,0 @@ -from rest_framework import serializers -from .models import LTCform, CPDAAdvanceform, CPDAReimbursementform, Leaveform, Appraisalform - -class LTC_serializer(serializers.ModelSerializer): - class Meta: - model = LTCform - fields = '__all__' - - def create(self, validated_data): - return LTCform.objects.create(**validated_data) - -class CPDAAdvance_serializer(serializers.ModelSerializer): - class Meta: - model = CPDAAdvanceform - fields = '__all__' - - def create(self, validated_data): - return CPDAAdvanceform.objects.create(**validated_data) - -class Appraisal_serializer(serializers.ModelSerializer): - class Meta: - model = Appraisalform - fields = '__all__' - - def create(self, validated_data): - return Appraisalform.objects.create(**validated_data) - -class CPDAReimbursement_serializer(serializers.ModelSerializer): - class Meta: - model = CPDAReimbursementform - fields = '__all__' - - def create(self, validated_data): - return CPDAReimbursementform.objects.create(**validated_data) - -class Leave_serializer(serializers.ModelSerializer): - class Meta: - model = Leaveform - fields = '__all__' - - def create(self, validated_data): - return Leaveform.objects.create(**validated_data) \ No newline at end of file diff --git a/FusionIIIT/applications/hr2/services.py b/FusionIIIT/applications/hr2/services.py new file mode 100644 index 000000000..84061094c --- /dev/null +++ b/FusionIIIT/applications/hr2/services.py @@ -0,0 +1,1688 @@ +""" +HR2 Module Services - Stub Implementation + +This is a stub implementation that allows the module to import without errors. +Full implementations will use actual Django models from models.py. +""" + +import datetime +import json +from decimal import Decimal, ROUND_HALF_UP + +from django.contrib.auth import get_user_model +from django.core.exceptions import ValidationError +from django.core.management.base import CommandError +from django.db import transaction +from django.utils import timezone + +from applications.globals.models import DepartmentInfo, Designation, ExtraInfo, HoldsDesignation, ModuleAccess +from applications.hr2.models import ( + AppraisalFormNew, + AppraisalPeriod, + CPDAAdvanceNew, + CPDAReimbursementNew, + Employee, + EmployeeAttendance, + EmployeeCategory, + EmployeeDetailsExtended, + EmployeeLeaveBalance, + FacultyWorkload, + LeaveApplicationNew, + LeaveType, + LTCApplicationNew, + PerformanceAppraisalNew, + PromotionApplication, + TrainingNomination, +) +from applications.hr2 import selectors as hr2_selectors + +try: + from notifications.signals import notify +except ImportError: # pragma: no cover - optional dependency + notify = None + +# ==================== CUSTOM EXCEPTIONS ==================== + +class InsufficientLeaveBalanceError(Exception): + """Raised when employee doesn't have enough leave balance.""" + pass + +class DuplicateLeaveApplicationError(Exception): + """Raised when overlapping leave already exists.""" + pass + +class InvalidWorkflowTransitionError(Exception): + """Raised when workflow transition is invalid.""" + pass + +class ResponsibilityNotAssignedError(Exception): + """Raised when responsibility is not assigned.""" + pass + +# ==================== LEAVE MANAGEMENT SERVICES ==================== + +def apply_for_leave(employee_extra_info, leave_type_id, from_date, to_date, reason, + address_during_leave="", contact_during_leave="", document=None, + academic_responsibility_user=None, academic_responsibility_designation=None, + administrative_responsibility_user=None, administrative_responsibility_designation=None): + """Apply for leave - STUB IMPLEMENTATION.""" + raise NotImplementedError("Leave application service not yet fully implemented. Using LeaveForm model.") + +def approve_leave_application(leave_app, approver_extra_info, remarks=""): + """Approve leave application - STUB IMPLEMENTATION.""" + raise NotImplementedError("Leave approval service not yet fully implemented.") + +def reject_leave_application(leave_app, approver_extra_info, remarks=""): + """Reject leave application - STUB IMPLEMENTATION.""" + raise NotImplementedError("Leave rejection service not yet fully implemented.") + +def handle_academic_responsibility(leave_app, approver_extra_info, action, remarks=""): + """Handle academic responsibility for leave - STUB IMPLEMENTATION.""" + raise NotImplementedError("Academic responsibility handler not yet fully implemented.") + +def handle_administrative_responsibility(leave_app, approver_extra_info, action, remarks=""): + """Handle administrative responsibility for leave - STUB IMPLEMENTATION.""" + raise NotImplementedError("Administrative responsibility handler not yet fully implemented.") + +# ==================== ATTENDANCE SERVICES ==================== + +def mark_attendance(employee_extra_info, date_val, status, in_time=None, out_time=None, remarks=""): + """Mark attendance - STUB IMPLEMENTATION.""" + raise NotImplementedError("Attendance marking service not yet fully implemented.") + +# ==================== FACULTY WORKLOAD SERVICES ==================== + +def calculate_faculty_workload(faculty_extra_info, semester, year): + """Calculate faculty workload - STUB IMPLEMENTATION.""" + raise NotImplementedError("Faculty workload calculation service not yet fully implemented.") + +# ==================== LTC SERVICES ==================== + +def apply_ltc(employee_extra_info, data): + """Apply for LTC - STUB IMPLEMENTATION.""" + raise NotImplementedError("LTC application service not yet fully implemented.") + +def approve_ltc(ltc_app, approver_extra_info, remarks=""): + """Approve LTC - STUB IMPLEMENTATION.""" + raise NotImplementedError("LTC approval service not yet fully implemented.") + +def reject_ltc(ltc_app, approver_extra_info, remarks=""): + """Reject LTC - STUB IMPLEMENTATION.""" + raise NotImplementedError("LTC rejection service not yet fully implemented.") + +# ==================== CPDA ADVANCE SERVICES ==================== + +def apply_cpda_advance(employee_extra_info, data): + """Apply for CPDA Advance - STUB IMPLEMENTATION.""" + raise NotImplementedError("CPDA Advance application service not yet fully implemented.") + +def approve_cpda_advance(cpda_adv, approver_extra_info, remarks=""): + """Approve CPDA Advance - STUB IMPLEMENTATION.""" + raise NotImplementedError("CPDA Advance approval service not yet fully implemented.") + +def reject_cpda_advance(cpda_adv, approver_extra_info, remarks=""): + """Reject CPDA Advance - STUB IMPLEMENTATION.""" + raise NotImplementedError("CPDA Advance rejection service not yet fully implemented.") + +# ==================== CPDA REIMBURSEMENT SERVICES ==================== + +def apply_cpda_reimbursement(employee_extra_info, data): + """Apply for CPDA Reimbursement - STUB IMPLEMENTATION.""" + raise NotImplementedError("CPDA Reimbursement application service not yet fully implemented.") + +def approve_cpda_reimbursement(cpda_reim, approver_extra_info, remarks=""): + """Approve CPDA Reimbursement - STUB IMPLEMENTATION.""" + raise NotImplementedError("CPDA Reimbursement approval service not yet fully implemented.") + +def reject_cpda_reimbursement(cpda_reim, approver_extra_info, remarks=""): + """Reject CPDA Reimbursement - STUB IMPLEMENTATION.""" + raise NotImplementedError("CPDA Reimbursement rejection service not yet fully implemented.") + +# ==================== APPRAISAL SERVICES ==================== + +def submit_appraisal(employee_extra_info, data): + """Submit appraisal - STUB IMPLEMENTATION.""" + raise NotImplementedError("Appraisal submission service not yet fully implemented.") + +def review_appraisal(appraisal_id, reviewer_extra_info, reviewer_scores, reviewer_remarks): + """Review appraisal - STUB IMPLEMENTATION.""" + raise NotImplementedError("Appraisal review service not yet fully implemented.") + + +# ==================== API WRITE SERVICES ==================== + +def _update_instance_from_data(instance, data): + for key, value in data.items(): + setattr(instance, key, value) + instance.save() + return instance + + +def update_instance(instance, validated_data): + return _update_instance_from_data(instance, validated_data) + + +def create_leave_application(request_user, validated_data): + employee_id = (validated_data.get('employee_id') or '').strip() + employee = hr2_selectors.get_employee_for_user(request_user) + if employee is None and employee_id: + employee = hr2_selectors.get_employee_by_id_optional(employee_id) + if employee is None: + raise ValidationError({"employee_id": "Employee profile not found."}) + + nominee_id = (validated_data.get('nominee_employee_id') or '').strip() + nominee_status = 'PENDING' if nominee_id else 'NOT_REQUIRED' + + role_flags = hr2_selectors.get_role_flags(employee.user) + is_director = role_flags.get('is_director') + is_hod = role_flags.get('is_hod') + is_registrar = role_flags.get('is_registrar') + is_hr_admin = role_flags.get('is_hr_admin') + is_accountant = role_flags.get('is_accountant') + + leave_type_name = (validated_data.get('leave_type') or '').strip() + is_cl_rh_leave = leave_type_name in ['Casual', 'Restricted'] + + employee_name = employee.user.get_full_name() or employee.user.username + department_name = employee.department.name if employee.department else (validated_data.get('department') or '') + designation_name = '' + designation_record = hr2_selectors.get_employee_current_designation_for_user(employee.user) + if designation_record: + designation_name = designation_record.full_name or designation_record.name + else: + designation_name = validated_data.get('designation') or '' + + approval_status = 'PENDING' + approver_role = '' + if is_director: + approval_status = 'APPROVED' + approver_role = 'Director' + elif is_registrar: + approval_status = 'FORWARDED' + approver_role = 'Director' + elif is_hod: + if is_cl_rh_leave: + approval_status = 'PENDING' + approver_role = 'HOD' + else: + approval_status = 'FORWARDED' + approver_role = 'Director' + elif is_hr_admin or is_accountant: + approval_status = 'FORWARDED' + approver_role = 'Registrar' + + data = dict(validated_data) + data.pop('employee_id', None) + data.pop('nominee_employee_id', None) + leave_app = LeaveApplicationNew( + employee=employee, + employee_name=employee_name, + department=department_name, + designation=designation_name, + handover_to=nominee_id, + nominee_status=nominee_status, + approval_status=approval_status, + current_approver_role=approver_role, + **data, + ) + leave_app.save() + + if is_director: + apply_leave_balance_for_approval(leave_app) + leave_app.save(update_fields=['leave_balance_before', 'leave_balance_after']) + + return leave_app + + +def update_leave_application(leave_app, validated_data): + return _update_instance_from_data(leave_app, validated_data) + + +def withdraw_leave_application(leave_app, user, remarks): + if leave_app.employee != user.extrainfo: + raise PermissionError("Not authorized") + if leave_app.approval_status not in ['PENDING', 'FORWARDED']: + raise ValidationError({"approval_status": "Only pending or forwarded requests can be withdrawn."}) + + role_flags = hr2_selectors.get_role_flags(user) + if role_flags.get('is_registrar'): + leave_app.approval_status = 'REJECTED' + leave_app.current_approver_role = 'Registrar' + elif role_flags.get('is_accountant'): + leave_app.approval_status = 'REJECTED' + leave_app.current_approver_role = 'Accountant' + elif role_flags.get('is_hr_admin'): + leave_app.approval_status = 'REJECTED' + leave_app.current_approver_role = 'HR Admin' + else: + leave_app.approval_status = 'WITHDRAWN' + leave_app.current_approver_role = 'Employee' + + leave_app.remarks = (remarks or '').strip() + leave_app.save(update_fields=['approval_status', 'current_approver_role', 'remarks']) + return leave_app + + +def request_leave_cancellation(leave_app, user, reason): + if leave_app.employee != user.extrainfo: + raise PermissionError("Not authorized") + if leave_app.approval_status != 'APPROVED': + raise ValidationError({"approval_status": "Only approved requests can be cancelled."}) + if leave_app.cancel_status != 'NOT_REQUESTED': + raise ValidationError({"cancel_status": "Cancellation already processed or pending."}) + + today = timezone.now().date() + if today >= leave_app.start_date: + raise ValidationError({"start_date": "Cancellation allowed only up to 1 day prior to start date."}) + + role_flags = hr2_selectors.get_role_flags(user) + requester_role = 'Employee' + if role_flags.get('is_director'): + requester_role = 'Director' + elif role_flags.get('is_hod'): + requester_role = 'HOD' + elif role_flags.get('is_registrar'): + requester_role = 'Registrar' + elif role_flags.get('is_accountant'): + requester_role = 'Accountant' + elif role_flags.get('is_hr_admin'): + requester_role = 'HR Admin' + + cancel_approver_role = 'HOD' + if requester_role in ['HOD', 'Director', 'Registrar']: + cancel_approver_role = 'Director' + elif requester_role in ['Accountant', 'HR Admin']: + cancel_approver_role = 'Registrar' + + leave_app.cancel_status = 'REQUESTED' + leave_app.cancel_requested_at = timezone.now() + leave_app.cancel_requested_by_role = requester_role + leave_app.cancel_current_approver_role = cancel_approver_role + leave_app.cancel_reason = (reason or '').strip() + leave_app.save(update_fields=[ + 'cancel_status', + 'cancel_requested_at', + 'cancel_requested_by_role', + 'cancel_current_approver_role', + 'cancel_reason', + ]) + return leave_app + + +def decide_leave_cancellation(leave_app, user, decision, remarks): + decision = (decision or '').lower() + if decision not in ['approve', 'reject']: + raise ValidationError({"decision": "Invalid decision"}) + if leave_app.cancel_status != 'REQUESTED': + raise ValidationError({"cancel_status": "No cancellation request pending."}) + + approver_role = (leave_app.cancel_current_approver_role or '').lower() + role_flags = hr2_selectors.get_role_flags(user) + allowed = False + if approver_role == 'hod': + allowed = role_flags.get('is_hod') + elif approver_role == 'director': + allowed = role_flags.get('is_director') + elif approver_role == 'registrar': + allowed = role_flags.get('is_registrar') + + if not allowed: + raise PermissionError("Not authorized") + + leave_app.cancel_decided_at = timezone.now() + leave_app.cancel_decision_remarks = (remarks or '').strip() + + if decision == 'approve': + leave_app.cancel_status = 'APPROVED' + leave_app.approval_status = 'CANCELLED' + leave_app.current_approver_role = leave_app.cancel_current_approver_role + restore_leave_balance_for_cancellation(leave_app) + else: + leave_app.cancel_status = 'REJECTED' + + leave_app.save(update_fields=[ + 'cancel_status', + 'cancel_decided_at', + 'cancel_decision_remarks', + 'approval_status', + 'current_approver_role', + 'leave_balance_before', + 'leave_balance_after', + ]) + return leave_app + + +def request_leave_extension(leave_app, user, new_end_date, reason): + if leave_app.employee != user.extrainfo: + raise PermissionError("Not authorized") + if leave_app.approval_status != 'APPROVED': + raise ValidationError({"approval_status": "Only approved requests can be extended."}) + if leave_app.extension_status != 'NOT_REQUESTED': + raise ValidationError({"extension_status": "Extension already processed or pending."}) + + today = timezone.now().date() + if today >= leave_app.end_date: + raise ValidationError({"end_date": "Extension allowed only before the original end date."}) + if new_end_date <= leave_app.end_date: + raise ValidationError({"end_date": "New end date must be after the current end date."}) + + new_total_days = Decimal((new_end_date - leave_app.start_date).days + 1) + + role_flags = hr2_selectors.get_role_flags(user) + requester_role = 'Employee' + if role_flags.get('is_director'): + requester_role = 'Director' + elif role_flags.get('is_hod'): + requester_role = 'HOD' + elif role_flags.get('is_registrar'): + requester_role = 'Registrar' + elif role_flags.get('is_accountant'): + requester_role = 'Accountant' + elif role_flags.get('is_hr_admin'): + requester_role = 'HR Admin' + + approver_role = 'HOD' + if requester_role in ['HOD', 'Director', 'Registrar']: + approver_role = 'Director' + elif requester_role in ['Accountant', 'HR Admin']: + approver_role = 'Registrar' + + leave_app.extension_status = 'REQUESTED' + leave_app.extension_requested_at = timezone.now() + leave_app.extension_requested_by_role = requester_role + leave_app.extension_current_approver_role = approver_role + leave_app.extension_reason = (reason or '').strip() + leave_app.extension_new_end_date = new_end_date + leave_app.extension_new_total_days = new_total_days + leave_app.save(update_fields=[ + 'extension_status', + 'extension_requested_at', + 'extension_requested_by_role', + 'extension_current_approver_role', + 'extension_reason', + 'extension_new_end_date', + 'extension_new_total_days', + ]) + return leave_app + + +def decide_leave_extension(leave_app, user, decision, remarks): + decision = (decision or '').lower() + if decision not in ['approve', 'reject']: + raise ValidationError({"decision": "Invalid decision"}) + if leave_app.extension_status != 'REQUESTED': + raise ValidationError({"extension_status": "No extension request pending."}) + + approver_role = (leave_app.extension_current_approver_role or '').lower() + role_flags = hr2_selectors.get_role_flags(user) + allowed = False + if approver_role == 'hod': + allowed = role_flags.get('is_hod') + elif approver_role == 'director': + allowed = role_flags.get('is_director') + elif approver_role == 'registrar': + allowed = role_flags.get('is_registrar') + + if not allowed: + raise PermissionError("Not authorized") + + leave_app.extension_decided_at = timezone.now() + leave_app.extension_decision_remarks = (remarks or '').strip() + + if decision == 'approve': + if not apply_leave_balance_for_extension(leave_app): + raise InsufficientLeaveBalanceError("Insufficient leave balance for extension.") + leave_app.extension_status = 'APPROVED' + leave_app.current_approver_role = leave_app.extension_current_approver_role + leave_app.end_date = leave_app.extension_new_end_date + leave_app.total_days = leave_app.extension_new_total_days + else: + leave_app.extension_status = 'REJECTED' + + leave_app.save(update_fields=[ + 'extension_status', + 'extension_decided_at', + 'extension_decision_remarks', + 'current_approver_role', + 'leave_balance_before', + 'leave_balance_after', + 'end_date', + 'total_days', + ]) + return leave_app + + +def submit_leave_resumption(leave_app, user, resumption_date, reason): + if leave_app.employee != user.extrainfo: + raise PermissionError("Not authorized") + if leave_app.approval_status != 'APPROVED': + raise ValidationError({"approval_status": "Resumption allowed only for approved leaves."}) + if leave_app.resumption_status != 'NOT_REQUESTED': + raise ValidationError({"resumption_status": "Resumption already submitted or processed."}) + if resumption_date <= leave_app.end_date: + raise ValidationError({"resumption_date": "Resumption date must be after the leave end date."}) + + leave_app.resumption_status = 'SUBMITTED' + leave_app.resumption_date = resumption_date + leave_app.resumption_reason = (reason or '').strip() + leave_app.resumption_submitted_at = timezone.now() + leave_app.resumption_current_approver_role = 'HOD' + leave_app.save(update_fields=[ + 'resumption_status', + 'resumption_date', + 'resumption_reason', + 'resumption_submitted_at', + 'resumption_current_approver_role', + ]) + return leave_app + + +def decide_leave_resumption(leave_app, user, decision, remarks): + decision = (decision or '').lower() + if decision not in ['approve', 'reject']: + raise ValidationError({"decision": "Invalid decision"}) + if leave_app.resumption_status != 'SUBMITTED': + raise ValidationError({"resumption_status": "No resumption request pending."}) + + role_flags = hr2_selectors.get_role_flags(user) + if not role_flags.get('is_hod'): + raise PermissionError("Not authorized") + + leave_app.resumption_decided_at = timezone.now() + leave_app.resumption_decision_remarks = (remarks or '').strip() + if decision == 'approve': + leave_app.resumption_status = 'APPROVED' + leave_app.current_approver_role = 'HOD' + else: + leave_app.resumption_status = 'REJECTED' + + leave_app.save(update_fields=[ + 'resumption_status', + 'resumption_decided_at', + 'resumption_decision_remarks', + 'current_approver_role', + ]) + return leave_app + + +def respond_leave_nominee(leave_app, user, action): + action = (action or '').lower() + if action not in ['accept', 'decline']: + raise ValidationError({"action": "Invalid action"}) + if leave_app.handover_to != user.extrainfo.id: + raise PermissionError("Not authorized") + + leave_app.nominee_status = 'ACCEPTED' if action == 'accept' else 'DECLINED' + leave_app.nominee_responded_at = datetime.datetime.utcnow() + leave_app.save(update_fields=['nominee_status', 'nominee_responded_at']) + return leave_app + + +def request_leave_document(leave_app, user, message): + if not message: + raise ValidationError({"message": "Document request message is required."}) + role_flags = hr2_selectors.get_role_flags(user) + if not role_flags.get('is_hod'): + raise PermissionError("Not authorized") + if leave_app.document_request_status == 'REQUESTED': + raise ValidationError({"document_request_status": "Document already requested."}) + + leave_app.document_request_message = message + leave_app.document_request_status = 'REQUESTED' + leave_app.document_requested_at = datetime.datetime.utcnow() + leave_app.save(update_fields=['document_request_message', 'document_request_status', 'document_requested_at']) + return leave_app + + +def submit_leave_document(leave_app, user, submission): + if not submission: + raise ValidationError({"submission": "Document submission is required."}) + if leave_app.employee != user.extrainfo: + raise PermissionError("Not authorized") + if leave_app.document_request_status != 'REQUESTED': + raise ValidationError({"document_request_status": "No document requested for this leave."}) + + leave_app.document_submission = submission + leave_app.document_request_status = 'SUBMITTED' + leave_app.document_submitted_at = datetime.datetime.utcnow() + leave_app.save(update_fields=['document_submission', 'document_request_status', 'document_submitted_at']) + return leave_app + + +def decide_leave_application(leave_app, user, decision, remarks): + decision = (decision or '').lower() + if decision not in ['approve', 'reject', 'forward']: + raise ValidationError({"decision": "Invalid decision"}) + + role_flags = hr2_selectors.get_role_flags(user) + approver_role = 'HOD' + if role_flags.get('is_registrar'): + approver_role = 'Registrar' + elif role_flags.get('is_director'): + approver_role = 'Director' + + leave_type_name = (leave_app.leave_type or '').strip() + is_cl_rh_leave = leave_type_name in ['Casual', 'Restricted'] + if decision == 'approve' and not is_cl_rh_leave and approver_role == 'HOD': + raise ValidationError({"decision": "Only CL/RH leaves can be approved by HOD. Please forward to Director."}) + if decision == 'forward' and is_cl_rh_leave: + decision = 'approve' + + if decision == 'approve': + leave_app.approval_status = 'APPROVED' + leave_app.current_approver_role = approver_role + apply_leave_balance_for_approval(leave_app) + elif decision == 'forward': + leave_app.approval_status = 'FORWARDED' + leave_app.current_approver_role = 'Director' + else: + leave_app.approval_status = 'REJECTED' + leave_app.current_approver_role = approver_role + + leave_app.remarks = remarks + leave_app.save(update_fields=[ + 'approval_status', + 'remarks', + 'current_approver_role', + 'leave_balance_before', + 'leave_balance_after', + ]) + return leave_app + + +def apply_leave_balance_for_approval(leave_app): + leave_type = hr2_selectors.get_leave_type_by_name(leave_app.leave_type) + if not leave_type: + return + year = leave_app.start_date.year + balance = hr2_selectors.get_leave_balance_for_employee_year( + leave_app.employee, + leave_type, + year, + ) + if balance is None: + balance = hr2_selectors.get_latest_leave_balance_for_employee(leave_app.employee, leave_type) + if balance is None or balance.year != year: + balance = EmployeeLeaveBalance( + employee=leave_app.employee, + leave_type=leave_type, + year=year, + opening_balance=Decimal('0'), + accrued=Decimal('0'), + availed=Decimal('0'), + current_balance=Decimal('0'), + ) + balance.save() + + total_days = Decimal(str(leave_app.total_days or 0)) + before_balance = balance.current_balance + balance.availed = (balance.availed or 0) + total_days + balance.current_balance = (balance.current_balance or 0) - total_days + balance.save(update_fields=['availed', 'current_balance']) + + if leave_app.leave_balance_before is None: + leave_app.leave_balance_before = before_balance + leave_app.leave_balance_after = balance.current_balance + + +def restore_leave_balance_for_cancellation(leave_app): + leave_type = hr2_selectors.get_leave_type_by_name(leave_app.leave_type) + if not leave_type: + return + year = leave_app.start_date.year + balance = hr2_selectors.get_leave_balance_for_employee_year( + leave_app.employee, + leave_type, + year, + ) + if balance is None: + balance = hr2_selectors.get_latest_leave_balance_for_employee(leave_app.employee, leave_type) + if balance is None: + return + + total_days = Decimal(str(leave_app.total_days or 0)) + before_balance = balance.current_balance + balance.availed = (balance.availed or 0) - total_days + balance.current_balance = (balance.current_balance or 0) + total_days + balance.save(update_fields=['availed', 'current_balance']) + + if leave_app.leave_balance_before is None: + leave_app.leave_balance_before = before_balance + leave_app.leave_balance_after = balance.current_balance + + +def apply_leave_balance_for_extension(leave_app): + if not leave_app.extension_new_total_days: + return False + delta_days = Decimal(str(leave_app.extension_new_total_days)) - Decimal(str(leave_app.total_days or 0)) + if delta_days <= 0: + return False + + leave_type = hr2_selectors.get_leave_type_by_name(leave_app.leave_type) + if not leave_type: + return False + year = leave_app.start_date.year + balance = hr2_selectors.get_leave_balance_for_employee_year( + leave_app.employee, + leave_type, + year, + ) + if balance is None: + balance = hr2_selectors.get_latest_leave_balance_for_employee(leave_app.employee, leave_type) + if balance is None: + return False + + if (balance.current_balance or 0) < delta_days: + return False + + before_balance = balance.current_balance + balance.availed = (balance.availed or 0) + delta_days + balance.current_balance = (balance.current_balance or 0) - delta_days + balance.save(update_fields=['availed', 'current_balance']) + + if leave_app.leave_balance_before is None: + leave_app.leave_balance_before = before_balance + leave_app.leave_balance_after = balance.current_balance + return True + + +def create_attendance(employee, validated_data): + attendance = EmployeeAttendance(employee=employee, **validated_data) + attendance.save() + return attendance + + +def create_performance_appraisal(employee, validated_data): + appraisal = PerformanceAppraisalNew(employee=employee, **validated_data) + appraisal.save() + return appraisal + + +def create_training_nomination(employee, nominated_by, validated_data): + nomination = TrainingNomination(employee=employee, nominated_by=nominated_by, **validated_data) + nomination.save() + return nomination + + +def create_promotion_application(employee, validated_data): + promotion = PromotionApplication(employee=employee, **validated_data) + promotion.save() + return promotion + + +def create_ltc_application(employee, validated_data): + ltc = LTCApplicationNew(employee=employee, **validated_data) + ltc.save() + return ltc + + +def update_ltc_application(ltc, validated_data): + return _update_instance_from_data(ltc, validated_data) + + +def withdraw_ltc_application(ltc, user, remarks): + if ltc.employee != user.extrainfo: + raise PermissionError("Not authorized") + if ltc.approval_status != 'PENDING': + raise ValidationError({"approval_status": "Only pending requests can be withdrawn."}) + + ltc.approval_status = 'WITHDRAWN' + ltc.remarks = (remarks or '').strip() + ltc.save(update_fields=['approval_status', 'remarks']) + return ltc + + +def decide_ltc_application(ltc, decision, remarks): + decision = (decision or '').lower() + if decision not in ['approve', 'reject', 'forward']: + raise ValidationError({"decision": "Invalid decision"}) + + if decision == 'approve': + ltc.approval_status = 'APPROVED' + ltc.accountant_status = 'APPROVED' + elif decision == 'forward': + ltc.approval_status = 'FORWARDED' + ltc.verified_by_hr = True + ltc.accountant_status = 'PENDING' + else: + ltc.approval_status = 'REJECTED' + ltc.accountant_status = 'REJECTED' + + ltc.remarks = remarks + ltc.save(update_fields=['approval_status', 'remarks', 'verified_by_hr', 'accountant_status']) + return ltc + + +def create_cpda_advance(employee, validated_data): + cpda = CPDAAdvanceNew(employee=employee, **validated_data) + cpda.save() + return cpda + + +def withdraw_cpda_advance(cpda, user, remarks): + if cpda.employee != user.extrainfo: + raise PermissionError("Not authorized") + if cpda.approval_status != 'PENDING': + raise ValidationError({"approval_status": "Only pending requests can be withdrawn."}) + + cpda.approval_status = 'WITHDRAWN' + cpda.remarks = (remarks or '').strip() + cpda.save(update_fields=['approval_status', 'remarks']) + return cpda + + +def decide_cpda_advance(cpda, user, decision, remarks): + decision = (decision or '').lower() + if decision not in ['approve', 'reject', 'forward-director']: + raise ValidationError({"decision": "Invalid decision"}) + + role_flags = hr2_selectors.get_role_flags(user) + if role_flags.get('is_hr_staff'): + if decision == 'reject': + cpda.approval_status = 'REJECTED' + cpda.accountant_processing_status = 'REJECTED' + else: + cpda.approval_status = 'FORWARDED' + cpda.verified_by_hr = True + cpda.accountant_processing_status = 'DIRECTOR_REVIEW' + elif role_flags.get('is_director'): + if decision == 'reject': + cpda.approval_status = 'REJECTED' + cpda.accountant_processing_status = 'REJECTED' + else: + cpda.approval_status = 'FORWARDED' + cpda.accountant_processing_status = 'PENDING' + elif role_flags.get('is_accountant'): + if decision == 'reject': + cpda.approval_status = 'REJECTED' + cpda.accountant_processing_status = 'REJECTED' + else: + cpda.approval_status = 'APPROVED' + cpda.accountant_processing_status = 'APPROVED' + else: + raise PermissionError("Not authorized") + + cpda.remarks = remarks + cpda.save(update_fields=['approval_status', 'remarks', 'verified_by_hr', 'accountant_processing_status']) + return cpda + + +def create_cpda_reimbursement(employee, validated_data): + reimbursement = CPDAReimbursementNew(employee=employee, **validated_data) + reimbursement.save() + return reimbursement + + +def decide_cpda_reimbursement(reimbursement, decision, reviewer, remarks): + decision = (decision or '').lower() + if decision == 'approve': + reimbursement.approval_status = 'APPROVED' + reimbursement.verified_by_hr = True + else: + reimbursement.approval_status = 'REJECTED' + reimbursement.remarks = remarks + reimbursement.save(update_fields=['approval_status', 'verified_by_hr', 'remarks']) + return reimbursement + + +def create_appraisal_form(employee, validated_data): + appraisal = AppraisalFormNew(employee=employee, **validated_data) + appraisal.save() + return appraisal + + +def review_appraisal_form(appraisal, user, action, remarks, rating): + role_flags = hr2_selectors.get_role_flags(user) + if role_flags.get('is_hod') and appraisal.assigned_reviewer_role.upper() != 'HOD': + raise PermissionError("Not assigned to HOD review.") + if role_flags.get('is_director') and appraisal.assigned_reviewer_role.upper() != 'DIRECTOR': + raise PermissionError("Not assigned to Director review.") + if not (role_flags.get('is_hod') or role_flags.get('is_director')): + raise PermissionError("Not authorized to review.") + + appraisal.reviewer_id = str(user.extrainfo.id) + appraisal.reviewer_comments = (remarks or '') + if rating: + appraisal.rating = str(rating) + + if action == 'approve': + appraisal.status = 'APPROVED' + appraisal.assigned_reviewer_role = '' + appraisal.assigned_reviewer = None + elif action == 'forward': + appraisal.status = 'REVIEWED' + appraisal.assigned_reviewer_role = 'DIRECTOR' + appraisal.assigned_reviewer = None + else: + appraisal.status = 'REVIEWED' + + appraisal.save(update_fields=[ + 'reviewer_id', + 'reviewer_comments', + 'rating', + 'status', + 'assigned_reviewer_role', + 'assigned_reviewer', + ]) + return appraisal + + +def assign_appraisal_reviewer(appraisal, user, role, reviewer_id): + role_flags = hr2_selectors.get_role_flags(user) + if not role_flags.get('is_hr_staff'): + raise PermissionError("Not authorized to assign.") + if role not in ['HOD', 'DIRECTOR']: + raise ValidationError({"role": "Role must be HOD or DIRECTOR."}) + if appraisal.status != 'PENDING': + raise ValidationError({"status": "Only pending appraisals can be assigned."}) + + assigned_reviewer = hr2_selectors.get_reviewer_by_id(reviewer_id) + if reviewer_id and not assigned_reviewer: + raise ValidationError({"reviewer_id": "Reviewer not found."}) + + appraisal.assigned_reviewer_role = role + appraisal.assigned_reviewer = assigned_reviewer + appraisal.assigned_by = user.extrainfo + appraisal.assigned_at = timezone.now() + appraisal.save(update_fields=[ + 'assigned_reviewer_role', + 'assigned_reviewer', + 'assigned_by', + 'assigned_at', + ]) + return appraisal + + +# ==================== MANAGEMENT COMMAND SERVICES ==================== + +DEFAULT_LEAVE_BALANCES = [ + ("Casual", "CL", 10), + ("Restricted", "RL", 5), + ("Medical", "ML", 12), + ("Earned", "EL", 18), + ("Vacation", "VL", 20), + ("Sabbatical", "SL", 0), +] + +ROLE_LEAVE_BALANCES = { + "EMP1002": {"CL": 12, "RL": 6, "ML": 15, "EL": 25, "VL": 30, "SL": 10}, + "EMP1003": {"CL": 15, "RL": 8, "ML": 20, "EL": 30, "VL": 35, "SL": 15}, + "EMP1004": {"CL": 12, "RL": 6, "ML": 15, "EL": 22, "VL": 28, "SL": 5}, + "EMP1005": {"CL": 10, "RL": 5, "ML": 12, "EL": 20, "VL": 25, "SL": 0}, + "EMP1006": {"CL": 10, "RL": 5, "ML": 12, "EL": 18, "VL": 22, "SL": 0}, + "EMP1007": {"CL": 12, "RL": 6, "ML": 15, "EL": 25, "VL": 30, "SL": 12}, +} + + +def seed_leave_balances(employee_id=None, seed_all=False, year=None): + if year is None: + year = datetime.date.today().year + + for name, code, _value in DEFAULT_LEAVE_BALANCES: + LeaveType.objects.get_or_create( + name=name, + code=code, + defaults={"is_active": True}, + ) + + if seed_all: + employees = ExtraInfo.objects.all() + else: + if not employee_id: + employee_id = "EMP1001" + try: + employees = [ExtraInfo.objects.get(id=employee_id)] + except ExtraInfo.DoesNotExist as exc: + raise CommandError(f"Employee not found: {employee_id}") from exc + + seeded_count = 0 + for employee in employees: + balance_map = ROLE_LEAVE_BALANCES.get(employee.id, {}) + for name, code, default_value in DEFAULT_LEAVE_BALANCES: + value = balance_map.get(code, default_value) + leave_type = LeaveType.objects.get(code=code) + EmployeeLeaveBalance.objects.update_or_create( + employee=employee, + leave_type=leave_type, + year=year, + defaults={ + "opening_balance": value, + "accrued": 0, + "availed": 0, + "current_balance": value, + }, + ) + seeded_count += 1 + + return {"seeded_count": seeded_count, "year": year} + + +def _parse_date(value): + if not value: + return None + return datetime.date.fromisoformat(value) + + +def _parse_gender(value): + if not value: + return "M" + value = value.strip().lower() + if value.startswith("f"): + return "F" + if value.startswith("m"): + return "M" + return "O" + + +def _split_name(full_name): + if not full_name: + return "", "" + parts = full_name.strip().split() + if len(parts) == 1: + return parts[0], "" + return parts[0], " ".join(parts[1:]) + + +def seed_hr_demo_data(): + departments = [ + "Computer Science and Engineering", + "Administration", + "Finance", + "Director Office", + "Electronics and Communication Engineering", + ] + + employees = [ + { + "employee_id": "EMP1009", + "name": "Dr. Sandeep Kumar", + "email": "sandeep.kumar@iiitdmj.ac.in", + "phone": "9876543225", + "gender": "Male", + "dob": "1978-09-14", + "department": "Electronics and Communication Engineering", + "designation": "Professor and HOD", + "role": "HOD", + "employment_type": "Permanent", + "date_of_joining": "2012-07-01", + "reporting_to": "EMP1003", + "status": "Active", + }, + { + "employee_id": "EMP1008", + "name": "Dr. Kiran Reddy", + "email": "kiran.reddy@iiitdmj.ac.in", + "phone": "9876543220", + "gender": "Male", + "dob": "1988-03-22", + "department": "Electronics and Communication Engineering", + "designation": "Associate Professor", + "role": "Employee", + "employment_type": "Permanent", + "date_of_joining": "2017-06-10", + "reporting_to": "EMP1009", # ECE HOD (create this later) + "status": "Active", + }, + { + "employee_id": "EMP1001", + "name": "Rahul Sharma", + "email": "rahul.sharma@iiitdmj.ac.in", + "phone": "9876543210", + "gender": "Male", + "dob": "1990-05-12", + "department": "Computer Science and Engineering", + "designation": "Assistant Professor", + "role": "Employee", + "employment_type": "Permanent", + "date_of_joining": "2021-08-01", + "reporting_to": "EMP1002", + "status": "Active", + }, + { + "employee_id": "EMP1007", + "name": "Dr. Anjali Mehta", + "email": "anjali.mehta@iiitdmj.ac.in", + "phone": "9876543216", + "gender": "Female", + "dob": "1985-11-08", + "department": "Computer Science and Engineering", + "designation": "Professor", + "role": "Employee", + "employment_type": "Permanent", + "date_of_joining": "2016-07-20", + "reporting_to": "EMP1002", + "status": "Active", + }, + { + "employee_id": "EMP1002", + "name": "Dr. Anil Kumar", + "email": "anil.kumar@iiitdmj.ac.in", + "phone": "9876543211", + "gender": "Male", + "dob": "1980-07-20", + "department": "Computer Science and Engineering", + "designation": "Professor and HOD", + "role": "HOD", + "employment_type": "Permanent", + "date_of_joining": "2015-06-15", + "reporting_to": "EMP1003", + "status": "Active", + }, + { + "employee_id": "EMP1003", + "name": "Dr. Meena Verma", + "email": "director@iiitdmj.ac.in", + "phone": "9876543212", + "gender": "Female", + "dob": "1975-02-11", + "department": "Director Office", + "designation": "Director", + "role": "Director", + "employment_type": "Permanent", + "date_of_joining": "2019-01-10", + "reporting_to": None, + "status": "Active", + }, + { + "employee_id": "EMP1004", + "name": "Suresh Verma", + "email": "registrar@iiitdmj.ac.in", + "phone": "9876543213", + "gender": "Male", + "dob": "1982-03-10", + "department": "Administration", + "designation": "Registrar", + "role": "Registrar", + "employment_type": "Permanent", + "date_of_joining": "2018-01-15", + "reporting_to": "EMP1003", + "status": "Active", + }, + { + "employee_id": "EMP1005", + "name": "Priya Nair", + "email": "hr.admin@iiitdmj.ac.in", + "phone": "9876543214", + "gender": "Female", + "dob": "1987-09-25", + "department": "Administration", + "designation": "HR Administrator", + "role": "HR Admin", + "employment_type": "Permanent", + "date_of_joining": "2020-11-05", + "reporting_to": "EMP1004", + "status": "Active", + }, + { + "employee_id": "EMP1006", + "name": "Arun Joshi", + "email": "accountant@iiitdmj.ac.in", + "phone": "9876543215", + "gender": "Male", + "dob": "1985-12-18", + "department": "Finance", + "designation": "Accountant", + "role": "Accountant", + "employment_type": "Permanent", + "date_of_joining": "2019-08-12", + "reporting_to": "EMP1004", + "status": "Active", + }, + ] + + users = [ + {"linked_employee_id": "EMP1009", "username": "hod_ece1009", "password": "hod123"}, + {"linked_employee_id": "EMP1008", "username": "kiran1008", "password": "kiran123"}, + {"linked_employee_id": "EMP1001", "username": "rahul1001", "password": "rahul123"}, + {"linked_employee_id": "EMP1007", "username": "anjali1007", "password": "anjali123"}, + {"linked_employee_id": "EMP1002", "username": "hod1002", "password": "hod123"}, + {"linked_employee_id": "EMP1003", "username": "director1003", "password": "director123"}, + {"linked_employee_id": "EMP1004", "username": "registrar1004", "password": "registrar123"}, + {"linked_employee_id": "EMP1005", "username": "hradmin1005", "password": "hradmin123"}, + {"linked_employee_id": "EMP1006", "username": "accountant1006", "password": "accountant123"}, + ] + + leave_balance = { + "employee_id": "EMP1001", + "casual_leave": 10, + "restricted_leave": 5, + "medical_leave": 12, + "earned_leave": 18, + "vacation_leave": 20, + "sabbatical_leave": 0, + } + leave_balance_hod_ece = { + "employee_id": "EMP1009", + "casual_leave": 15, + "restricted_leave": 8, + "medical_leave": 20, + "earned_leave": 30, + "vacation_leave": 35, + "sabbatical_leave": 15, + } + + leave_request = { + "employee_id": "EMP1001", + "employee_name": "Rahul Sharma", + "department": "Computer Science and Engineering", + "designation": "Assistant Professor", + "leave_type": "Casual", + "start_date": "2026-04-10", + "end_date": "2026-04-12", + "total_days": 3, + "reason": "Personal work", + "contact_during_leave": "9876543210", + "address_during_leave": "Jabalpur, MP", + "handover_notes": "Classes handed over to Dr. X", + "attachment_file": "", + "leave_balance_before": 10, + "leave_balance_after": 7, + "approval_status": "PENDING", + "current_approver_role": "HOD", + "remarks": "", + } + + appraisal_request = { + "employee_id": "EMP1001", + "employee_name": "Rahul Sharma", + "department": "Computer Science and Engineering", + "designation": "Assistant Professor", + "appraisal_year": "2025-2026", + "self_summary": "Completed teaching and research responsibilities effectively.", + "teaching_performance": "Good", + "research_work": "Worked on 2 projects", + "publications": "1 journal paper", + "trainings_attended": "AI workshop", + "administrative_contributions": "Exam coordination", + "goals_achieved": "Completed syllabus and guided students", + "future_goals": "Publish more papers", + "reviewer_id": "EMP1002", + "status": "PENDING", + "remarks": "", + } + + ltc_request = { + "employee_id": "EMP1001", + "employee_name": "Rahul Sharma", + "department": "Computer Science and Engineering", + "designation": "Assistant Professor", + "ltc_block_year": "2024-2027", + "travel_start_date": "2026-05-05", + "travel_end_date": "2026-05-12", + "destination": "Delhi", + "purpose_of_travel": "Family travel", + "family_members": [{"name": "Priya Sharma", "relationship": "Spouse"}], + "travel_mode": "Train", + "ticket_number": "IRCTC12345", + "ticket_cost": 12000, + "accommodation_cost": 8000, + "other_expenses": 2000, + "total_amount_claimed": 22000, + "tickets_upload": "", + "bills_upload": "", + "previous_ltc_used": True, + "last_ltc_date": "2023-06-15", + "verified_by_hr": False, + "approval_status": "PENDING", + "accountant_status": "Not Started", + "remarks": "", + } + + cpda_request = { + "employee_id": "EMP1001", + "employee_name": "Rahul Sharma", + "department": "Computer Science and Engineering", + "designation": "Assistant Professor", + "event_name": "National Conference on AI", + "event_type": "Conference", + "organized_by": "IIT Delhi", + "venue": "New Delhi", + "start_date": "2026-06-20", + "end_date": "2026-06-22", + "registration_fee": 5000, + "travel_expense": 8000, + "accommodation_expense": 6000, + "other_expenses": 1000, + "total_amount": 20000, + "purpose_of_attending": "Present paper and improve research skills", + "benefits_to_institution": "Research development and academic exposure", + "invitation_letter": "", + "receipts": "", + "certificates": "", + "verified_by_hr": False, + "approval_status": "PENDING", + "accountant_processing_status": "Not Started", + "remarks": "", + } + + with transaction.atomic(): + for name in departments: + DepartmentInfo.objects.get_or_create(name=name) + + teaching_category, _ = EmployeeCategory.objects.get_or_create( + name="Teaching", defaults={"category_type": "TEACHING"} + ) + non_teaching_category, _ = EmployeeCategory.objects.get_or_create( + name="Non-Teaching", defaults={"category_type": "NON_TEACHING"} + ) + + user_lookup = {item["linked_employee_id"]: item for item in users} + + for employee in employees: + user_info = user_lookup.get(employee["employee_id"], {}) + username = user_info.get("username") or employee["employee_id"].lower() + first_name, last_name = _split_name(employee["name"]) + + user, created = get_user_model().objects.get_or_create( + username=username, + defaults={ + "email": employee["email"], + "first_name": first_name, + "last_name": last_name, + }, + ) + if created and user_info.get("password"): + user.set_password(user_info["password"]) + user.save() + + department_obj = DepartmentInfo.objects.get(name=employee["department"]) + + extra_info, _ = ExtraInfo.objects.get_or_create( + id=employee["employee_id"], + defaults={ + "user": user, + "sex": _parse_gender(employee["gender"]), + "date_of_birth": _parse_date(employee["dob"]), + "user_type": "faculty" + if employee["department"] == "Computer Science and Engineering" + else "staff", + "department": department_obj, + "phone_no": int(employee["phone"]), + "address": "", + }, + ) + + category = teaching_category if extra_info.user_type == "faculty" else non_teaching_category + EmployeeDetailsExtended.objects.get_or_create( + extra_info=extra_info, + defaults={ + "category": category, + "date_of_joining": _parse_date(employee["date_of_joining"]), + "appointment_type": employee["employment_type"], + }, + ) + + designation_type = "academic" if extra_info.user_type == "faculty" else "administrative" + designation, _ = Designation.objects.get_or_create( + name=employee["designation"], + defaults={ + "full_name": employee["designation"], + "type": designation_type, + }, + ) + + HoldsDesignation.objects.get_or_create( + user=user, + working=user, + designation=designation, + ) + + leave_types = [ + ("Casual", "CL", leave_balance["casual_leave"]), + ("Restricted", "RL", leave_balance["restricted_leave"]), + ("Medical", "ML", leave_balance["medical_leave"]), + ("Earned", "EL", leave_balance["earned_leave"]), + ("Vacation", "VL", leave_balance["vacation_leave"]), + ("Sabbatical", "SL", leave_balance["sabbatical_leave"]), + ] + + for name, code, _value in leave_types: + LeaveType.objects.get_or_create( + name=name, + code=code, + defaults={"is_active": True}, + ) + + # Seed leave balances for EMP1001 (default) and EMP1009 (ECE HOD) + for lb in [leave_balance, leave_balance_hod_ece]: + employee_user = ExtraInfo.objects.get(id=lb["employee_id"]) + year = datetime.date.today().year + for name, code, value in [ + ("Casual", "CL", lb["casual_leave"]), + ("Restricted", "RL", lb["restricted_leave"]), + ("Medical", "ML", lb["medical_leave"]), + ("Earned", "EL", lb["earned_leave"]), + ("Vacation", "VL", lb["vacation_leave"]), + ("Sabbatical", "SL", lb["sabbatical_leave"]), + ]: + leave_type = LeaveType.objects.get(code=code) + EmployeeLeaveBalance.objects.update_or_create( + employee=employee_user, + leave_type=leave_type, + year=year, + defaults={ + "opening_balance": value, + "accrued": 0, + "availed": 0, + "current_balance": value, + }, + ) + + LeaveApplicationNew.objects.get_or_create( + employee=employee_user, + start_date=_parse_date(leave_request["start_date"]), + end_date=_parse_date(leave_request["end_date"]), + defaults={ + "employee_name": leave_request["employee_name"], + "department": leave_request["department"], + "designation": leave_request["designation"], + "leave_type": leave_request["leave_type"], + "total_days": leave_request["total_days"], + "reason": leave_request["reason"], + "contact_during_leave": leave_request["contact_during_leave"], + "address_during_leave": leave_request["address_during_leave"], + "handover_to": "Dr. X", + "handover_notes": leave_request["handover_notes"], + "medical_certificate": "", + "attachment_file": leave_request["attachment_file"], + "leave_balance_before": leave_request["leave_balance_before"], + "leave_balance_after": leave_request["leave_balance_after"], + "approval_status": leave_request["approval_status"], + "current_approver_role": leave_request["current_approver_role"], + "remarks": leave_request["remarks"], + }, + ) + + AppraisalFormNew.objects.get_or_create( + employee=employee_user, + appraisal_year=appraisal_request["appraisal_year"], + defaults={ + "employee_name": appraisal_request["employee_name"], + "department": appraisal_request["department"], + "designation": appraisal_request["designation"], + "self_summary": appraisal_request["self_summary"], + "key_responsibilities": "Teaching, research, and academic mentoring.", + "achievements": appraisal_request["goals_achieved"], + "challenges_faced": "", + "teaching_performance": appraisal_request["teaching_performance"], + "research_work": appraisal_request["research_work"], + "publications": appraisal_request["publications"], + "projects_handled": "", + "administrative_contributions": appraisal_request["administrative_contributions"], + "trainings_attended": appraisal_request["trainings_attended"], + "certifications": "", + "workshops": "", + "goals_achieved": appraisal_request["goals_achieved"], + "future_goals": appraisal_request["future_goals"], + "supporting_documents": "", + "reviewer_id": appraisal_request["reviewer_id"], + "reviewer_comments": "", + "rating": "", + "status": appraisal_request["status"], + "remarks": appraisal_request["remarks"], + }, + ) + + block_year = int(ltc_request["ltc_block_year"].split("-")[0]) + LTCApplicationNew.objects.get_or_create( + employee=employee_user, + travel_start_date=_parse_date(ltc_request["travel_start_date"]), + travel_end_date=_parse_date(ltc_request["travel_end_date"]), + defaults={ + "employee_name": ltc_request["employee_name"], + "department": ltc_request["department"], + "designation": ltc_request["designation"], + "ltc_block_year": block_year, + "destination": ltc_request["destination"], + "purpose_of_travel": ltc_request["purpose_of_travel"], + "family_members": json.dumps(ltc_request["family_members"]), + "relationship_details": "Spouse", + "travel_mode": ltc_request["travel_mode"], + "ticket_number": ltc_request["ticket_number"], + "ticket_cost": ltc_request["ticket_cost"], + "accommodation_cost": ltc_request["accommodation_cost"], + "other_expenses": ltc_request["other_expenses"], + "total_amount_claimed": ltc_request["total_amount_claimed"], + "tickets_upload": ltc_request["tickets_upload"], + "bills_upload": ltc_request["bills_upload"], + "previous_ltc_used": ltc_request["previous_ltc_used"], + "last_ltc_date": _parse_date(ltc_request["last_ltc_date"]), + "verified_by_hr": ltc_request["verified_by_hr"], + "approval_status": ltc_request["approval_status"], + "accountant_status": ltc_request["accountant_status"], + "remarks": ltc_request["remarks"], + }, + ) + + CPDAAdvanceNew.objects.get_or_create( + employee=employee_user, + start_date=_parse_date(cpda_request["start_date"]), + end_date=_parse_date(cpda_request["end_date"]), + defaults={ + "employee_name": cpda_request["employee_name"], + "department": cpda_request["department"], + "designation": cpda_request["designation"], + "event_name": cpda_request["event_name"], + "event_type": cpda_request["event_type"], + "organized_by": cpda_request["organized_by"], + "venue": cpda_request["venue"], + "registration_fee": cpda_request["registration_fee"], + "travel_expense": cpda_request["travel_expense"], + "accommodation_expense": cpda_request["accommodation_expense"], + "other_expenses": cpda_request["other_expenses"], + "total_amount": cpda_request["total_amount"], + "purpose_of_attending": cpda_request["purpose_of_attending"], + "benefits_to_institution": cpda_request["benefits_to_institution"], + "invitation_letter": cpda_request["invitation_letter"], + "receipts": cpda_request["receipts"], + "certificates": cpda_request["certificates"], + "verified_by_hr": cpda_request["verified_by_hr"], + "approval_status": cpda_request["approval_status"], + "accountant_processing_status": cpda_request["accountant_processing_status"], + "remarks": cpda_request["remarks"], + }, + ) + + return {"employees_seeded": len(employees)} + + +def seed_hr2_demo_data(): + User = get_user_model() + now = timezone.now() + + department, _ = DepartmentInfo.objects.get_or_create(name="Computer Science") + + designation, _ = Designation.objects.get_or_create( + name="Faculty", + defaults={"full_name": "Faculty", "type": "academic"}, + ) + + module_access, _ = ModuleAccess.objects.get_or_create(designation="Faculty") + if not module_access.hr: + module_access.hr = True + module_access.save() + + user, created = User.objects.get_or_create( + username="rahul123", + defaults={ + "first_name": "Rahul", + "last_name": "Sharma", + "email": "rahul.sharma@iiitdmj.ac.in", + }, + ) + if created: + user.set_password("user@123") + user.save() + else: + user.email = "rahul.sharma@iiitdmj.ac.in" + user.first_name = user.first_name or "Rahul" + user.last_name = user.last_name or "Sharma" + user.set_password("user@123") + user.save() + + extra_info, _ = ExtraInfo.objects.get_or_create( + id="EMP001", + defaults={ + "user": user, + "title": "Dr.", + "sex": "M", + "date_of_birth": "1990-05-12", + "user_status": "PRESENT", + "address": "IIITDMJ Campus", + "phone_no": 9876543210, + "user_type": "faculty", + "department": department, + "about_me": "Faculty member", + "last_selected_role": "Faculty", + }, + ) + if extra_info.user_id != user.id: + extra_info.user = user + extra_info.department = department + extra_info.phone_no = 9876543210 + extra_info.last_selected_role = "Faculty" + extra_info.save() + + HoldsDesignation.objects.get_or_create( + user=user, + working=user, + designation=designation, + ) + + Employee.objects.get_or_create( + id=user, + defaults={ + "father_name": "Rajesh Sharma", + "mother_name": "Sunita Sharma", + "category": "General", + "caste": "N/A", + "home_state": "Madhya Pradesh", + "home_district": "Jabalpur", + "full_address": "IIITDMJ Campus, Dumna Airport Road", + "date_of_joining": "2021-08-01", + "date_of_birth": "1990-05-12", + "blood_group": "O+", + "phone_number": "9876543210", + "personal_email": "rahul.sharma@iiitdmj.ac.in", + "emergency_contact_number": "9876543211", + "emergency_contact_name": "Rajesh Sharma", + "employee_type": "Faculty", + }, + ) + + leave_type_map = { + "Casual": ("CL", Decimal("10")), + "Earned": ("EL", Decimal("18")), + "Medical": ("ML", Decimal("12")), + "Restricted": ("RL", Decimal("5")), + "Vacation": ("VL", Decimal("25")), + "Sabbatical": ("SL", Decimal("0")), + } + + current_year = now.year + for name, (code, balance) in leave_type_map.items(): + leave_type, _ = LeaveType.objects.get_or_create( + name=name, + defaults={"code": code, "is_active": True}, + ) + EmployeeLeaveBalance.objects.get_or_create( + employee=extra_info, + leave_type=leave_type, + year=current_year, + defaults={ + "opening_balance": balance, + "accrued": Decimal("0"), + "availed": Decimal("0"), + "current_balance": balance, + }, + ) + + if notify: + notify.send( + sender=user, + recipient=user, + verb="Welcome to HR Portal", + description="Welcome to HR Portal", + ) + + return {"employee_id": extra_info.id} + + +def convert_vl_to_earned(source_year=None, dry_run=False): + if source_year is None: + source_year = datetime.date.today().year + target_year = source_year + 1 + + vl_type = LeaveType.objects.filter(code__iexact="VL").first() or LeaveType.objects.filter(name__iexact="Vacation").first() + el_type = LeaveType.objects.filter(code__iexact="EL").first() or LeaveType.objects.filter(name__iexact="Earned").first() + + if not vl_type or not el_type: + raise CommandError("Leave types VL/Earned not found. Ensure LeaveType records exist.") + + all_employees = ExtraInfo.objects.all() + converted_count = 0 + total_converted = Decimal("0.0") + + next_year_defaults = { + "CL": Decimal("8.0"), + "RL": Decimal("2.0"), + "VL": Decimal("60.0"), + } + leave_types = {lt.code.upper(): lt for lt in LeaveType.objects.all() if lt.code} + + for employee in all_employees: + is_faculty = employee.user_type == "faculty" + converted = Decimal("0.0") + + vl_balance = EmployeeLeaveBalance.objects.filter( + employee=employee, + leave_type=vl_type, + year=source_year, + ).first() + if is_faculty and vl_balance: + vl_current = Decimal(str(vl_balance.current_balance or 0)) + if vl_current > 0: + converted = (vl_current / Decimal("2")).quantize(Decimal("0.1"), rounding=ROUND_HALF_UP) + if not dry_run: + vl_balance.current_balance = Decimal("0.0") + vl_balance.save(update_fields=["current_balance"]) + if converted > 0: + converted_count += 1 + total_converted += converted + + if dry_run: + continue + + for code, leave_type in leave_types.items(): + if code == "EL": + opening = Decimal("0.0") + accrued = converted + current = converted + elif code in next_year_defaults: + opening = next_year_defaults[code] + accrued = Decimal("0.0") + current = opening + else: + opening = Decimal("0.0") + accrued = Decimal("0.0") + current = Decimal("0.0") + + EmployeeLeaveBalance.objects.update_or_create( + employee=employee, + leave_type=leave_type, + year=target_year, + defaults={ + "opening_balance": opening, + "accrued": accrued, + "availed": Decimal("0.0"), + "current_balance": current, + }, + ) + + return { + "dry_run": dry_run, + "converted_count": converted_count, + "total_converted": total_converted, + "source_year": source_year, + "target_year": target_year, + } \ No newline at end of file diff --git a/FusionIIIT/applications/hr2/test.py b/FusionIIIT/applications/hr2/test.py deleted file mode 100644 index 89ec7917f..000000000 --- a/FusionIIIT/applications/hr2/test.py +++ /dev/null @@ -1,116 +0,0 @@ -def ltc_pre_processing(request): - ltc_form_data = {} - - # Extract general information - ltc_form_data['name'] = request.POST['name'] - ltc_form_data['block_year'] = int(request.POST['block_year']) - ltc_form_data['pf_no'] = int(request.POST['pf_no']) - ltc_form_data['basic_pay_salary'] = int(request.POST['basic_pay_salary']) - ltc_form_data['designation'] = request.POST['designation'] - ltc_form_data['department_info'] = request.POST['department_info'] - ltc_form_data['leave_availability'] = request.POST.getlist('leave_availability') == ['True', 'True'] - ltc_form_data['leave_start_date'] = request.POST['leave_start_date'] - ltc_form_data['leave_end_date'] = request.POST['leave_end_date'] - ltc_form_data['date_of_leave_for_family'] = request.POST['date_of_leave_for_family'] - ltc_form_data['nature_of_leave'] = request.POST['nature_of_leave'] - ltc_form_data['purpose_of_leave'] = request.POST['purpose_of_leave'] - ltc_form_data['hometown_or_not'] = request.POST.get('hometown_or_not') == 'True' - ltc_form_data['place_of_visit'] = request.POST['place_of_visit'] - ltc_form_data['address_during_leave'] = request.POST['address_during_leave'] - - # Extract details of family members - family_members = [] - for i in range(1, 7): - if request.POST.get(f'info_{i}_1'): - family_member = ','.join(request.POST.getlist(f'info_{i}_{j}')[0] for j in range(1, 4)) - family_members.append(family_member) - ltc_form_data['details_of_family_members_already_done'] = ','.join(family_members) - - # Extract details of dependents - dependents = [] - for i in range(1, 7): - if request.POST.get(f'd_info_{i}_1'): - dependent = ','.join(request.POST.getlist(f'd_info_{i}_{j}')[0] for j in range(1, 5)) - dependents.append(dependent) - ltc_form_data['details_of_dependents'] = ','.join(dependents) - - # Extract remaining fields - ltc_form_data['amount_of_advance_required'] = int(request.POST['amount_of_advance_required']) - ltc_form_data['certified_family_dependents'] = request.POST['certified_family_dependents'] - ltc_form_data['certified_advance'] = int(request.POST['certified_advance']) - ltc_form_data['adjusted_month'] = request.POST['adjusted_month'] - ltc_form_data['date'] = request.POST['date'] - ltc_form_data['phone_number_for_contact'] = int(request.POST['phone_number_for_contact']) - - return ltc_form_data - -# Example usage -request_data = { - 'csrfmiddlewaretoken': ['yLyPMZMWRBnDU3hSh5kPGq6AgOFNY5WTK1HaZxAuiozCzXBf8qfOML5irZJd8MkM'], - 'block_year': ['232'], - 'pf_no': ['222'], - 'basic_pay_salary': ['2322'], - 'name': ['dsds'], - 'designation': ['dsdsd'], - 'department_info': ['ds'], - 'leave_availability': ['True', 'True'], - 'leave_start_date': ['2024-02-22'], - 'leave_end_date': ['2024-02-22'], - 'date_of_leave_for_family': ['2024-02-22'], - 'nature_of_leave': ['dsds'], - 'purpose_of_leave': ['dsdsd'], - 'hometown_or_not': ['True'], - 'place_of_visit': [''], - 'address_during_leave': ['full street address'], - 'details_of_family_members_already_done': ['sds', 'dsd', 'dsd'], - 'info_1_1': ['1'], - 'info_1_2': ['dsds'], - 'info_1_3': ['12'], - 'info_2_1': ['2'], - 'info_2_2': ['sds'], - 'info_2_3': ['121'], - 'info_3_1': ['3'], - 'info_3_2': ['dsds'], - 'info_3_3': ['21'], - 'info_4_1': [''], - 'info_4_2': [''], - 'info_4_3': [''], - 'info_5_1': [''], - 'info_5_2': [''], - 'info_5_3': [''], - 'info_6_1': [''], - 'info_6_2': [''], - 'info_6_3': [''], - 'd_info_1_1': ['1'], - 'd_info_1_2': ['sds'], - 'd_info_1_3': ['21'], - 'd_info_1_4': ['sdd'], - 'd_info_2_1': ['2'], - 'd_info_2_2': ['dsd'], - 'd_info_2_3': ['23'], - 'd_info_2_4': ['sds'], - 'd_info_3_1': ['3'], - 'd_info_3_2': ['sd'], - 'd_info_3_3': ['21'], - 'd_info_3_4': ['dds'], - 'd_info_4_1': [''], - 'd_info_4_2': [''], - 'd_info_4_3': [''], - 'd_info_4_4': [''], - 'd_info_5_1': [''], - 'd_info_5_2': [''], - 'd_info_5_3': [''], - 'd_info_5_4': [''], - 'd_info_6_1': [''], - 'd_info_6_2': [''], - 'd_info_6_3': [''], - 'd_info_6_4': [''], - 'amount_of_advance_required': ['211'], - 'certified_family_dependents': ['dqwd'], - 'certified_advance': ['dqwd'], - 'adjusted_month': ['qwdwd'], - 'date': ['2024-02-22'], - 'phone_number_for_contact': ['2312123'] -} - -print(ltc_pre_processing(request_data)) diff --git a/FusionIIIT/applications/hr2/tests.py b/FusionIIIT/applications/hr2/tests.py deleted file mode 100644 index 7ce503c2d..000000000 --- a/FusionIIIT/applications/hr2/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/FusionIIIT/applications/hr2/urls.py b/FusionIIIT/applications/hr2/urls.py index cfaddcec6..8c45e4555 100644 --- a/FusionIIIT/applications/hr2/urls.py +++ b/FusionIIIT/applications/hr2/urls.py @@ -1,100 +1,7 @@ -from django.conf.urls import url, include - -from applications.hr2 import views -from applications.hr2.api import form_views +from django.urls import path, include app_name = 'hr2' urlpatterns = [ - - url(r'^$', views.service_book, name='hr2'), - url(r'^hradmin/$', views.hr_admin, name='hradmin'), - url(r'^edit/(?P\d+)/$', views.edit_employee_details, - name='editEmployeeDetails'), - url(r'^viewdetails/(?P\d+)/$', - views.view_employee_details, name='viewEmployeeDetails'), - url(r'^editServiceBook/(?P\d+)/$', - views.edit_employee_servicebook, name='editServiceBook'), - url(r'^administrativeProfile/$', views.administrative_profile, - name='administrativeProfile'), - url(r'^addnew/$', views.add_new_user, name='addnew'), - url(r'^ltc_form/(?P\d+)/$', views.ltc_form, - name='ltcForm'), - - url(r'^view_ltc_form/(?P\d+)/$', views.view_ltc_form, - name='view_ltc_form'), - url(r'^form_mangement_ltc/',views.form_mangement_ltc, name='form_mangement_ltc'), - url(r'dashboard/', views.dashboard, name='dashboard'), - url(r'^form_mangement_ltc_hr/(?P\d+)/$',views.form_mangement_ltc_hr, name='form_mangement_ltc_hr'), - url(r'^form_mangement_ltc_hod/',views.form_mangement_ltc_hod, name='form_mangement_ltc_hod'), - url(r'^search_employee/', views.search_employee, name='search_employee'), - url(r'^track_file/(?P\d+)/$', views.track_file, name='track_file'), - url('form_view_ltc/(?P\d+)/$', views.form_view_ltc, name='form_view_ltc'), - # url('file_handle/', views.file_handle, name='file_handle'), - url('file_handle_cpda/', views.file_handle_cpda, name='file_handle_cpda'), - url('file_handle_leave/', views.file_handle_leave, name='file_handle_leave'), - url('file_handle_ltc/', views.file_handle_ltc, name='file_handle_ltc'), - url('file_handle_appraisal/', views.file_handle_appraisal, name='file_handle_appraisal'), - url('file_handle_cpda_reimbursement/', views.file_handle_cpda_reimbursement, name='file_handle_cpda_reimbursement'), - - url(r'^cpda_form/(?P\d+)/$', views.cpda_form,name='cpdaForm'), - url(r'^view_cpda_form/(?P\d+)/$', views.view_cpda_form,name='view_cpda_form'), - url(r'^form_mangement_cpda/',views.form_mangement_cpda, name='form_mangement_cpda'), - url(r'^form_mangement_cpda_hr/(?P\d+)/$',views.form_mangement_cpda_hr, name='form_mangement_cpda_hr'), - url(r'^form_mangement_cpda_hod/',views.form_mangement_cpda_hod, name='form_mangement_cpda_hod'), - url('form_view_cpda/(?P\d+)/$', views.form_view_cpda, name='form_view_cpda'), - url(r'^api/',include('applications.hr2.api.urls')), - - url(r'^cpda_reimbursement_form/(?P\d+)/$', views.cpda_reimbursement_form,name='cpdaReimbursementForm'), - url(r'^view_cpda_reimbursement_form/(?P\d+)/$', views.view_cpda_reimbursement_form,name='view_cpda_reimbursement_form'), - url(r'form_view_cpda_reimbursement/(?P\d+)/$', views.form_view_cpda_reimbursement, name='form_view_cpda_reimbursement'), - url(r'^form_mangement_cpda_reimbursement/',views.form_mangement_cpda_reimbursement, name='form_mangement_cpda_reimbursement'), - url(r'^form_mangement_cpda_reimbursement_hr/(?P\d+)/$',views.form_mangement_cpda_reimbursement_hr, name='form_mangement_cpda_reimbursement_hr'), - url(r'^form_mangement_cpda_reimbursement_hod/',views.form_mangement_cpda_reimbursement_hod, name='form_mangement_cpda_reimbursement_hod'), - - url(r'^leave_form/(?P\d+)/$', views.leave_form,name='leaveForm'), - url(r'^view_leave_form/(?P\d+)/$', views.view_leave_form,name='view_leave_form'), - url(r'^form_mangement_leave/',views.form_mangement_leave, name='form_mangement_leave'), - url(r'^form_mangement_leave_hr/(?P\d+)/$',views.form_mangement_leave_hr, name='form_mangement_leave_hr'), - url(r'^form_mangement_leave_hod/',views.form_mangement_leave_hod, name='form_mangement_leave_hod'), - url('form_view_leave/(?P\d+)/$', views.form_view_leave, name='form_view_leave'), - - - - url(r'^appraisal_form/(?P\d+)/$', views.appraisal_form,name='appraisalForm'), - url(r'^view_appraisal_form/(?P\d+)/$', views.view_appraisal_form,name='view_appraisal_form'), - url(r'^form_mangement_appraisal/',views.form_mangement_appraisal, name='form_mangement_appraisal'), - url(r'^form_mangement_appraisal_hr/(?P\d+)/$',views.form_mangement_appraisal_hr, name='form_mangement_appraisal_hr'), - - url(r'^form_view_appraisal/(?P\d+)/$', views.form_view_appraisal, name='form_view_appraisal'), - url(r'^getform/$', views.getform , name='getform'), - url(r'^getformcpdaAdvance/$', views.getformcpdaAdvance , name='getformcpdaAdvance'), - url(r'^getformLeave/$', views.getformLeave , name='getformLeave'), - url(r'^getformAppraisal/$', views.getformAppraisal , name='getformAppraisal'), - url(r'^getformcpdaReimbursement/$', views.getformcpdaReimbursement , name='getformcpdaReimbursement'), - - - - - - - - - - - - - - - - - - - - - - - - - + path('api/', include('applications.hr2.api.urls')), ] diff --git a/FusionIIIT/applications/hr2/views.py b/FusionIIIT/applications/hr2/views.py deleted file mode 100644 index 939564d30..000000000 --- a/FusionIIIT/applications/hr2/views.py +++ /dev/null @@ -1,2514 +0,0 @@ -import json -from django.shortcuts import render, get_object_or_404 -from .models import * -from applications.globals.models import ExtraInfo -from applications.globals.models import * -from django.db.models import Q -from django.http import Http404 -from .forms import EditDetailsForm, EditConfidentialDetailsForm, EditServiceBookForm, NewUserForm, AddExtraInfo -from django.contrib import messages -from applications.eis.models import * -from django.http import HttpResponse, HttpResponseRedirect -from applications.establishment.models import * -from applications.establishment.views import * -from applications.eis.models import * -from applications.globals.models import ExtraInfo, HoldsDesignation, DepartmentInfo, Designation - -from html import escape -from io import BytesIO -import re -from rest_framework import status -from decimal import Decimal - - -from django.contrib.auth.models import User -from django.http import HttpResponse, HttpResponseRedirect -from django.shortcuts import (get_object_or_404, redirect, render, - render) -from django.http import JsonResponse -from applications.filetracking.sdk.methods import * -from django.core.files.base import File as DjangoFile -from django.views.decorators.csrf import csrf_exempt - - -def edit_employee_details(request, id): - """ Views for edit details""" - template = 'hr2Module/editDetails.html' - - try: - employee = ExtraInfo.objects.get(user__id=id) - except: - raise Http404("Post does not exist") - - if request.method == "POST": - for e in request.POST: - print(e) - print('--------------') - form = EditDetailsForm(request.POST) - conf_form = EditConfidentialDetailsForm(request.POST, request.FILES) - print("f1", form.is_valid()) - print("f2", conf_form.is_valid()) - if form.is_valid() and conf_form.is_valid(): - form.save() - conf_form.save() - try: - ee = ExtraInfo.objects.get(pk=id) - ee.user_status = "PRESENT" - ee.save() - - except: - pass - messages.success(request, "Employee details edited successfully") - else: - messages.warning(request, "Error in submitting form") - pass - else: - print("Failed") - - form = EditDetailsForm(initial={'extra_info': employee.id}) - conf_form = EditConfidentialDetailsForm(initial={'extra_info': employee}) - context = {'form': form, 'confForm': conf_form, 'employee': employee} - - return render(request, template, context) - - -def hr_admin(request): - """ Views for HR2 Admin page """ - - user = request.user - # extra_info = ExtraInfo.objects.select_related().get(user=user) - designat = HoldsDesignation.objects.select_related().get(user=user) - print(designat) - if designat.designation.name == 'hradmin': - template = 'hr2Module/hradmin.html' - # searched employee - query = request.GET.get('search') - if(request.method == "GET"): - if(query != None): - emp = ExtraInfo.objects.filter( - Q(user__first_name__icontains=query) | - Q(user__last_name__icontains=query) | - Q(id__icontains=query) - ).distinct() - emp = emp.filter(user_type="faculty") - else: - emp = ExtraInfo.objects.all() - emp = emp.filter(user_type="faculty") - else: - emp = ExtraInfo.objects.all() - emp = emp.filter(user_type="faculty") - empPresent = emp.filter(user_status="PRESENT") - empNew = emp.filter(user_status="NEW") - context = {'emps': emp, "empPresent": empPresent, "empNew": empNew} - print(context) - return render(request, template, context) - else: - return HttpResponse('Unauthorized', status=401) - - -def service_book(request): - """ - Views for service book page - """ - user = request.user - extra_info = ExtraInfo.objects.select_related().get(user=user) - - lien_service_book = ForeignService.objects.filter( - extra_info=extra_info).filter(service_type="LIEN").order_by('-start_date') - deputation_service_book = ForeignService.objects.filter( - extra_info=extra_info).filter(service_type="DEPUTATION").order_by('-start_date') - other_service_book = ForeignService.objects.filter( - extra_info=extra_info).filter(service_type="OTHER").order_by('-start_date') - appraisal_form = EmpAppraisalForm.objects.filter( - extra_info=extra_info).order_by('-year') - pf = extra_info.id - workAssignemnt = WorkAssignemnt.objects.filter( - extra_info_id=pf).order_by('-start_date') - - empprojects = emp_research_projects.objects.filter( - pf_no=pf).order_by('-start_date') - visits = emp_visits.objects.filter(pf_no=pf).order_by('-entry_date') - conferences = emp_confrence_organised.objects.filter( - pf_no=pf).order_by('-date_entry') - template = 'hr2Module/servicebook.html' - awards = emp_achievement.objects.filter(pf_no=pf).order_by('-date_entry') - thesis = emp_mtechphd_thesis.objects.filter( - pf_no=pf).order_by('-date_entry') - context = {'lienServiceBooks': lien_service_book, 'deputationServiceBooks': deputation_service_book, 'otherServiceBooks': other_service_book, - 'appraisalForm': appraisal_form, - 'empproject': empprojects, - 'visits': visits, - 'conferences': conferences, - 'awards': awards, - 'thesis': thesis, - 'extrainfo': extra_info, - 'workAssignment': workAssignemnt, - 'awards': awards - } - - return HttpResponseRedirect("/eis/profile/") - # return render(request, template, context) - - -def view_employee_details(request, id): - """ Views for edit details""" - extra_info = ExtraInfo.objects.get(user__id=id) - context = {} - try: - emp = Employee.objects.get(extra_info=extra_info) - context['emp'] = emp - except: - print("Personal details not found") - # try: - - # except: - # extra_info = ExtraInfo.objects.get(pk=id) - # print("caught error") - # return - lien_service_book = ForeignService.objects.filter( - extra_info=extra_info).filter(service_type="LIEN").order_by('-start_date') - deputation_service_book = ForeignService.objects.filter( - extra_info=extra_info).filter(service_type="DEPUTATION").order_by('-start_date') - other_service_book = ForeignService.objects.filter( - extra_info=extra_info).filter(service_type="OTHER").order_by('-start_date') - appraisal_form = EmpAppraisalForm.objects.filter( - extra_info=extra_info).order_by('-year') - pf = extra_info.user.id - print(pf) - workAssignemnt = WorkAssignemnt.objects.filter( - extra_info_id=pf).order_by('-start_date') - - empprojects = emp_research_projects.objects.filter( - pf_no=pf).order_by('-start_date') - visits = emp_visits.objects.filter(pf_no=pf).order_by('-entry_date') - conferences = emp_confrence_organised.objects.filter( - pf_no=pf).order_by('-date_entry') - awards = emp_achievement.objects.filter(pf_no=pf).order_by('-date_entry') - thesis = emp_mtechphd_thesis.objects.filter( - pf_no=pf).order_by('-date_entry') - - response = {} - # Check if establishment variables exist, if not create some fields or ask for them - response.update(initial_checks(request)) - if is_eligible(request) and request.method == "POST": - handle_appraisal(request) - - if is_eligible(request): - response.update(generate_appraisal_lists(request)) - - # If user has designation "HOD" - if is_hod(request): - response.update(generate_appraisal_lists_hod(request)) - - # If user has designation "Director" - if is_director(request): - response.update(generate_appraisal_lists_director(request)) - - response.update({'cpda': False, 'ltc': False, - 'appraisal': True, 'leave': False}) - # designat = HoldsDesignation.objects.get(user=request.user).designation - template = 'hr2Module/viewdetails.html' - context.update({'lienServiceBooks': lien_service_book, 'deputationServiceBooks': deputation_service_book, 'otherServiceBooks': other_service_book, 'user': extra_info.user, 'extrainfo': extra_info, - 'appraisalForm': appraisal_form, - 'empproject': empprojects, - 'visits': visits, - 'conferences': conferences, - 'awards': awards, - 'thesis': thesis, - 'workAssignment': workAssignemnt, - # 'designat':designat, - - }) - context.update(response) - - return render(request, template, context) - - -def edit_employee_servicebook(request, id): - """ Views for edit Service Book details""" - template = 'hr2Module/editServiceBook.html' - - try: - employee = ExtraInfo.objects.get(user__id=id) - except: - raise Http404("Post does not exist") - - if request.method == "POST": - form = EditServiceBookForm(request.POST, request.FILES) - - if form.is_valid(): - form.save() - messages.success( - request, "Employee Service Book details edited successfully") - else: - messages.warning(request, "Error in submitting form") - pass - - form = EditServiceBookForm(initial={'extra_info': employee.id}) - context = {'form': form, 'employee': employee - } - - return render(request, template, context) - - -def administrative_profile(request, username=None): - user = get_object_or_404( - User, username=username) if username else request.user - extra_info = get_object_or_404(ExtraInfo, user=user) - if extra_info.user_type != 'faculty' and extra_info.user_type != 'staff': - return redirect('/') - pf = extra_info.id - - lien_service_book = ForeignService.objects.filter( - extra_info=extra_info).filter(service_type="LIEN").order_by('-start_date') - deputation_service_book = ForeignService.objects.filter( - extra_info=extra_info).filter(service_type="DEPUTATION").order_by('-start_date') - other_service_book = ForeignService.objects.filter( - extra_info=extra_info).filter(service_type="OTHER").order_by('-start_date') - - response = {} - - response.update(initial_checks(request)) - if is_eligible(request) and request.method == "POST": - handle_appraisal(request) - - if is_eligible(request): - response.update(generate_appraisal_lists(request)) - - # If user has designation "HOD" - if is_hod(request): - response.update(generate_appraisal_lists_hod(request)) - - # If user has designation "Director" - if is_director(request): - response.update(generate_appraisal_lists_director(request)) - - response.update({'cpda': False, 'ltc': False, - 'appraisal': True, 'leave': False}) - workAssignemnt = WorkAssignemnt.objects.filter( - extra_info_id=pf).order_by('-start_date') - - context = {'user': user, - 'pf': pf, - 'lienServiceBooks': lien_service_book, 'deputationServiceBooks': deputation_service_book, 'otherServiceBooks': other_service_book, - 'extrainfo': extra_info, - 'workAssignment': workAssignemnt - } - - context.update(response) - template = 'hr2Module/dashboard_hr.html' - return render(request, template, context) - -def chkValidity(password): - flag = 0 - while True: - if (len(password)<8): - flag = -1 - break - elif not re.search("[a-z]", password): - flag = -1 - break - elif not re.search("[0-9]", password): - flag = -1 - break - elif not re.search("[_@$]", password): - flag = -1 - break - elif re.search("\s", password): - flag = -1 - break - else: - return True - break - - if flag ==-1: - return False - -def add_new_user(request): - """ Views for edit Service Book details""" - template = 'hr2Module/add_new_employee.html' - - if request.method == "POST": - form = NewUserForm(request.POST) - eform = AddExtraInfo(request.POST) - - if form.is_valid(): - user = form.save() - messages.success(request, "New User added Successfully") - else: - t_pass = '0000' - if 'password1' in request.POST: - t_pass = request.POST['password1'] - # messages.error(request,str(type(t_pass))) - if chkValidity(t_pass): - messages.error(request,"User already exists") - elif not t_pass == '0000': - messages.error(request,"Use Stronger Password") - else: - messages.error(request,"User already exists") - - - if eform.is_valid(): - eform.save() - messages.success(request, "Extra info of user saved successfully") - elif not eform.is_valid: - messages.error(request,"Some error occured") - - form = NewUserForm - eform = AddExtraInfo - - try: - employee = ExtraInfo.objects.all().first() - except: - raise Http404("Post does not exist") - - - context = {'employee': employee, "register_form": form, "eform": eform - } - - return render(request, template, context) - - - -def ltc_pre_processing(request): - data = {} - detailsOfFamilyMembersAlreadyDone = "" - - for memeber in request.POST.getlist('detailsOfFamilyMembersAlreadyDone'): - if(memeber == ""): - detailsOfFamilyMembersAlreadyDone = detailsOfFamilyMembersAlreadyDone + 'None' + ',' - else: - detailsOfFamilyMembersAlreadyDone = detailsOfFamilyMembersAlreadyDone + memeber + ',' - - data['detailsOfFamilyMembersAlreadyDone'] = detailsOfFamilyMembersAlreadyDone.rstrip(',') - - - detailsOfFamilyMembersAboutToAvail = "" - - for i in range(1,4): - for j in range(1,4): - key_is = f'info_{i}_{j}' - - if(request.POST.get(key_is) == ""): - detailsOfFamilyMembersAboutToAvail = detailsOfFamilyMembersAboutToAvail + 'None' + ',' - else: - detailsOfFamilyMembersAboutToAvail = detailsOfFamilyMembersAboutToAvail + request.POST.get(key_is) + ',' - - data['detailsOfFamilyMembersAboutToAvail'] = detailsOfFamilyMembersAboutToAvail.rstrip(',') - - - detailsOfDependents = "" - - for i in range(1,7): - for j in range(1,5): - key_is = f'd_info_{i}_{j}' - if(request.POST.get(key_is) == ""): - detailsOfDependents = detailsOfDependents + 'None' + ',' - else: - detailsOfDependents = detailsOfDependents + request.POST.get(key_is) + ',' - - data['detailsOfDependents'] = detailsOfDependents.rstrip(',') - - return data - - -def reverse_ltc_pre_processing(data): - reversed_data = {} - - # Copying over simple key-value pairs - simple_keys = [ - 'blockYear', - 'pfNo', - 'basicPaySalary', - 'name', - 'designation', - 'departmentInfo', - 'leaveRequired', - 'leaveStartDate', - 'leaveEndDate', - 'dateOfDepartureForFamily', - 'natureOfLeave', - 'purposeOfLeave', - 'hometownOrNot', - 'placeOfVisit', - 'addressDuringLeave', - 'amountOfAdvanceRequired', - 'certifiedThatFamilyDependents', - 'certifiedThatAdvanceTakenOn', - 'adjustedMonth', - 'submissionDate', - 'phoneNumberForContact' - ] - - - for key in simple_keys: - value = getattr(data, key) - reversed_data[key] = value if value != 'None' else '' - - # Reversing array-like values - reversed_data['detailsOfFamilyMembersAlreadyDone'] = getattr(data,'detailsOfFamilyMembersAlreadyDone').split(',') - - detailsOfFamilyMembersAboutToAvail = getattr(data,'detailsOfFamilyMembersAboutToAvail').split(',') - for index, value in enumerate(detailsOfFamilyMembersAboutToAvail): - detailsOfFamilyMembersAboutToAvail[index] = value if value != 'None' else '' - - reversed_data['info_1_1'] = detailsOfFamilyMembersAboutToAvail[0] - reversed_data['info_1_2'] = detailsOfFamilyMembersAboutToAvail[1] - reversed_data['info_1_3'] = detailsOfFamilyMembersAboutToAvail[2] - reversed_data['info_2_1'] = detailsOfFamilyMembersAboutToAvail[3] - reversed_data['info_2_2'] = detailsOfFamilyMembersAboutToAvail[4] - reversed_data['info_2_3'] = detailsOfFamilyMembersAboutToAvail[5] - reversed_data['info_3_1'] = detailsOfFamilyMembersAboutToAvail[6] - reversed_data['info_3_2'] = detailsOfFamilyMembersAboutToAvail[7] - reversed_data['info_3_3'] = detailsOfFamilyMembersAboutToAvail[8] - - # # Reversing details_of_dependents - detailsOfDependents = getattr(data,'detailsOfDependents').split(',') - for i in range(1, 7): - for j in range(1, 5): - key = f'd_info_{i}_{j}' - value = detailsOfDependents.pop(0) - reversed_data[key] = value if value != 'None' else '' - - return reversed_data - -def get_designation_by_user_id(user_id): - try: - # Query HoldsDesignation model to get the user's designation - designation_objs = HoldsDesignation.objects.filter(user=user_id) - return designation_objs.first().designation - except ExtraInfo.DoesNotExist: - return None - except HoldsDesignation.DoesNotExist: - return None - -def search_employee(request): - search_text = request.GET.get('search', '') - data = {'designation': 'Assistant Professor'} - try: - - employee = User.objects.get(username = search_text) - - - holds_designation = HoldsDesignation.objects.filter(user=employee) - holds_designation = list(holds_designation) - - - - data['designation'] = str(holds_designation[0].designation) - except ExtraInfo.DoesNotExist: - data = {'error': "Employee doesn't exist"} - - return JsonResponse(data) - -def ltc_form(request, id): - """ Views for edit details""" - try: - employee = ExtraInfo.objects.get(user__id=id) - except: - raise Http404("Employee does not exist! id doesnt exist") - user_id = id - creator = User.objects.get(id = user_id) - - if(employee.user_type == 'faculty' or employee.user_type == 'staff' or employee.user_type == 'student'): - template = 'hr2Module/ltc_form.html' - - if request.method == "POST": - try: - - data = ltc_pre_processing(request) - - - form1 = { - 'employeeId': id, - 'name': request.POST.get('name'), - 'blockYear': request.POST.get('blockYear'), - 'basicPaySalary': request.POST.get('basicPaySalary'), - 'designation': request.POST.get('designation'), - 'pfNo': request.POST.get('pfNo'), - 'departmentInfo': request.POST.get('departmentInfo'), - 'leaveRequired': request.POST.get('leaveRequired'), - 'leaveStartDate': request.POST.get('leaveStartDate'), - 'leaveEndDate': request.POST.get('leaveEndDate'), - 'dateOfDepartureForFamily': request.POST.get('dateOfDepartureForFamily'), - 'natureOfLeave': request.POST.get('natureOfLeave'), - 'purposeOfLeave': request.POST.get('purposeOfLeave'), - 'hometownOrNot': request.POST.get('hometownOrNot'), - 'placeOfVisit': request.POST.get('placeOfVisit'), - 'addressDuringLeave': request.POST.get('addressDuringLeave'), - 'detailsOfFamilyMembersAlreadyDone': data['detailsOfFamilyMembersAlreadyDone'], - 'detailsOfFamilyMembersAboutToAvail': data['detailsOfFamilyMembersAboutToAvail'], - 'detailsOfDependents': data['detailsOfDependents'], - 'amountOfAdvanceRequired': request.POST.get('amountOfAdvanceRequired'), - 'certifiedThatFamilyDependents': request.POST.get('certifiedThatFamilyDependents'), - 'certifiedThatAdvanceTakenOn': request.POST.get('certifiedThatAdvanceTakenOn'), - 'adjustedMonth': request.POST.get('adjustedMonth'), - 'submissionDate': request.POST.get('submissionDate'), - 'phoneNumberForContact': request.POST.get('phoneNumberForContact'), - 'username_employee': request.POST.get('username_employee'), - 'designation_employee': request.POST.get('designation_employee'), - 'created_by' : creator, - } - - - try: - ltc_form = LTCform.objects.create( - employeeId=id, - name=request.POST.get('name'), - blockYear=request.POST.get('blockYear'), - pfNo=request.POST.get('pfNo'), - basicPaySalary=request.POST.get('basicPaySalary'), - designation=request.POST.get('designation'), - departmentInfo=request.POST.get('departmentInfo'), - leaveRequired=request.POST.get('leaveAvailability'), - leaveStartDate=request.POST.get('leaveStartDate'), - leaveEndDate=request.POST.get('leaveEndDate'), - dateOfDepartureForFamily=request.POST.get('dateOfLeaveForFamily'), - natureOfLeave=request.POST.get('natureOfLeave'), - purposeOfLeave=request.POST.get('purposeOfLeave'), - hometownOrNot=request.POST.get('hometownOrNot'), - placeOfVisit=request.POST.get('placeOfVisit'), - addressDuringLeave=request.POST.get('addressDuringLeave'), - detailsOfFamilyMembersAlreadyDone=data['detailsOfFamilyMembersAlreadyDone'], - detailsOfFamilyMembersAboutToAvail=data['detailsOfFamilyMembersAboutToAvail'], - detailsOfDependents=data['detailsOfDependents'], - amountOfAdvanceRequired=request.POST.get('amountOfAdvanceRequired'), - certifiedThatFamilyDependents=request.POST.get('certifiedThatFamilyDependents'), - certifiedThatAdvanceTakenOn=request.POST.get('certifiedThatAdvanceTakenOn'), - adjustedMonth=request.POST.get('adjustedMonth'), - submissionDate=request.POST.get('submissionDate'), - phoneNumberForContact=request.POST.get('phoneNumberForContact'), - created_by=creator, - ) - - except Exception as e: - - print("An error occurred while creating the LTC form:", e) - - - - - uploader = employee.user - uploader_designation = 'Assistant Professor' - - get_designation = get_designation_by_user_id(employee.user) - if(get_designation): - uploader_designation = get_designation - - receiver = request.POST.get('username_employee') - receiver_designation = request.POST.get('designation_employee') - src_module = "HR" - src_object_id = str(ltc_form.id) - file_extra_JSON = {"type": "LTC"} - - - # Create a file representing the LTC form and send it to HR admin - file_id = create_file( - uploader=uploader, - uploader_designation=uploader_designation, - receiver=receiver, - receiver_designation=receiver_designation, - src_module=src_module, - src_object_id=src_object_id, - file_extra_JSON=file_extra_JSON, - attached_file=None # Attach any file if necessary - ) - - - messages.success(request, "Ltc form filled successfully!") - - - return redirect(request.path_info) - - except Exception as e: - messages.warning(request, "Fill not correctly") - context = {'employee': employee} - return render(request, template, context) - - - - # Query all LTC requests - ltc_requests = LTCform.objects.filter(employeeId=id) - - username = employee.user - uploader_designation = 'Assistant Professor' - - - designation = get_designation_by_user_id(employee.user) - if(designation): - uploader_designation = designation - - - inbox = view_inbox(username = username, designation = uploader_designation, src_module = "HR") - archived_files = view_archived(username = username, designation = uploader_designation, src_module = "HR") - filtered_inbox = [] - for i in inbox: - item = i.get('file_extra_JSON', {}) - if item.get('type') == 'LTC': - filtered_inbox.append(i) - - filtered_archived_files = [] - for i in archived_files: - item = i.get('file_extra_JSON', {}) - if item.get('type') == 'LTC': - filtered_archived_files.append(i) - - - - - - - context = {'employee': employee, 'ltc_requests': ltc_requests, 'inbox': filtered_inbox , 'designation':designation, 'archived_files': filtered_archived_files , 'user_id': user_id} - - return render(request, template, context) - else: - return render(request, 'hr2Module/edit.html') - -def form_view_ltc(request , id): - ltc_request = get_object_or_404(LTCform, id=id) - - user_id = ltc_request.created_by.id - - from_user = request.GET.get('param1') - from_designation = request.GET.get('param2') - file_id = request.GET.get('param3') - - template = 'hr2Module/view_ltc_form.html' - ltc_request = reverse_ltc_pre_processing(ltc_request) - - context = {'ltc_request' : ltc_request , "button" : 1 , "file_id" : file_id, "from_user" :from_user , "from_designation" : from_designation ,"id" : id, "user_id" : user_id} - - return render(request , template , context) - -def track_file(request, id): - # Assuming file_history is a list of dictionaries - template = 'hr2Module/ltc_form_trackfile.html' - file_history = view_history(file_id=id) - - - context = {'file_history': file_history} - - # Create a JSON response - return render(request ,template , context) - -def get_current_file_owner(file_id: int) -> User: - ''' - This functions returns the current owner of the file. - The current owner is the latest recipient of the file - ''' - latest_tracking = Tracking.objects.filter( - file_id=file_id).order_by('-receive_date').first() - latest_recipient = latest_tracking.receiver_id - return latest_recipient - -def file_handle_leave(request): - if request.method == 'POST': - form_data2 = request.POST - form_data=request.POST.get('context') - action = form_data2.get('action') - - form_data=json.loads(form_data) - form_id = form_data['form_id'] - file_id = form_data['file_id'] - from_user = form_data['from_user'] - from_designation = form_data['from_designation'] - username_employee = form_data['username_employee'] - designation_employee = form_data['designation_employee'] - - remark = form_data['remark_id'] - - - #database - leave_form = LeaveForm.objects.get(id=form_id) - - leave_form.save() - - #database - try: - leave_form = LeaveForm.objects.get(id=form_id) - except LeaveForm.DoesNotExist: - return JsonResponse({"error": "LeaveForm object with the provided ID does not exist"}, status=404) - - - - current_owner = get_current_file_owner(file_id) - - # if action value is 0 then forward the file - # if action value is 1 then reject the file - # if action value is 3 then approve the file - # otherwise archive the file - - if(action == '0'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = username_employee, receiver_designation = designation_employee,remarks = f"Forwarded by {current_owner} to {username_employee}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = username_employee, receiver_designation = designation_employee,remarks = f"Forwarded by {current_owner} to {username_employee}, Reason : {remark}", file_extra_JSON = "None") - messages.success(request, "File forwarded successfully") - elif(action == '1'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = leave_form.name, receiver_designation = leave_form.designation, remarks = f"Rejected by {current_owner}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = leave_form.name, receiver_designation = leave_form.designation, remarks = f"Rejected by {current_owner}, Reason : {remark}", file_extra_JSON = "None") - messages.success(request, "File rejected successfully") - elif(action == '2'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = leave_form.name, receiver_designation = leave_form.designation, remarks = f"Approved by {current_owner}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = leave_form.name, receiver_designation = leave_form.designation, remarks = f"Approved by {current_owner}, Reason : {remark}", file_extra_JSON = "None") - leave_form.approved = True - leave_form.approvedDate = timezone.now() - leave_form.approved_by = current_owner - leave_form.save() - messages.success(request, "File approved successfully") - else: - is_archived = archive_file(file_id=file_id) - if( is_archived ): - messages.error(request, "Error in file archived") - else: - messages.success(request, "Success in file archived") - - - return HttpResponse("Success") - else: - - return HttpResponse("Failure") - - -def file_handle_cpda(request): - if request.method == 'POST': - form_data2 = request.POST - form_data=request.POST.get('context') - action = form_data2.get('action') - - form_data=json.loads(form_data) - form_id = form_data['form_id'] - file_id = form_data['file_id'] - from_user = form_data['from_user'] - from_designation = form_data['from_designation'] - username_employee = form_data['username_employee'] - designation_employee = form_data['designation_employee'] - - advanceAmountPDA = form_data['advanceAmountPDA'] - balanceAvailable = form_data['balanceAvailable'] - amountCheckedInPDA = form_data['amountCheckedInPDA'] - - remark = form_data['remark_id'] - #change - - - #database - try: - cpda_form = CPDAAdvanceform.objects.get(id=form_id) - except CPDAAdvanceform.DoesNotExist: - return JsonResponse({"error": "CPDAform object with the provided ID does not exist"}, status=404) - - - if advanceAmountPDA == "": - advanceAmountPDA = None - else: - advanceAmountPDA = Decimal(advanceAmountPDA) - - if balanceAvailable == "": - balanceAvailable = None - else: - balanceAvailable = Decimal(balanceAvailable) - - if amountCheckedInPDA == "": - amountCheckedInPDA = None - else: - amountCheckedInPDA = Decimal(amountCheckedInPDA) - - - - - # Update the attribute - setattr(cpda_form, "advanceAmountPDA", advanceAmountPDA) - setattr(cpda_form, "balanceAvailable", balanceAvailable) - setattr(cpda_form, "amountCheckedInPDA", amountCheckedInPDA) - cpda_form.save() - - #database - try: - cpda_form = CPDAAdvanceform.objects.get(id=form_id) - except CPDAAdvanceform.DoesNotExist: - return JsonResponse({"error": "CPDAform object with the provided ID does not exist"}, status=404) - - - current_owner = get_current_file_owner(file_id) - - # if action value is 0 then forward the file - # if action value is 1 then reject the file - # if action value is 3 then approve the file - # otherwise archive the file - - if(action == '0'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = username_employee, receiver_designation = designation_employee,remarks = f"Forwarded by {current_owner} to {username_employee}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = username_employee, receiver_designation = designation_employee,remarks = f"Forwarded by {current_owner} to {username_employee}, Reason : {remark}", file_extra_JSON = "None") - messages.success(request, "File forwarded successfully") - elif(action == '1'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = cpda_form.name, receiver_designation = cpda_form.designation, remarks = f"Rejected by {current_owner}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = cpda_form.name, receiver_designation = cpda_form.designation, remarks = f"Rejected by {current_owner}, Reason : {remark}", file_extra_JSON = "None") - messages.success(request, "File rejected successfully") - elif(action == '2'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = cpda_form.name, receiver_designation = cpda_form.designation, remarks = f"Approved by {current_owner}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = cpda_form.name, receiver_designation = cpda_form.designation, remarks = f"Approved by {current_owner}, Reason : {remark}", file_extra_JSON = "None") - cpda_form.approved = True - cpda_form.approvedDate = timezone.now() - cpda_form.approved_by = current_owner - cpda_form.save() - messages.success(request, "File approved successfully") - else: - is_archived = archive_file(file_id=file_id) - if( is_archived ): - messages.error(request, "Error in file archived") - else: - messages.success(request, "Success in file archived") - - - return HttpResponse("Success") - else: - - return HttpResponse("Failure") - - - -def file_handle_cpda_reimbursement(request): - if request.method == 'POST': - form_data2 = request.POST - form_data=request.POST.get('context') - action = form_data2.get('action') - - form_data=json.loads(form_data) - form_id = form_data['form_id'] - file_id = form_data['file_id'] - from_user = form_data['from_user'] - from_designation = form_data['from_designation'] - username_employee = form_data['username_employee'] - designation_employee = form_data['designation_employee'] - - advanceDueAdjustment = form_data['advanceDueAdjustment'] - balanceAvailable = form_data['balanceAvailable'] - amountCheckedInPDA = form_data['amountCheckedInPDA'] - - remark = form_data['remark_id'] - #change - - - #database - try: - cpda_form = CPDAReimbursementform.objects.get(id=form_id) - except CPDAReimbursementform.DoesNotExist: - return JsonResponse({"error": "CPDAReimbursementform object with the provided ID does not exist"}, status=404) - - - if advanceDueAdjustment == "": - advanceDueAdjustment = None - else: - advanceDueAdjustment = Decimal(advanceDueAdjustment) - - if balanceAvailable == "": - balanceAvailable = None - else: - balanceAvailable = Decimal(balanceAvailable) - - if amountCheckedInPDA == "": - amountCheckedInPDA = None - else: - amountCheckedInPDA = Decimal(amountCheckedInPDA) - - - # Update the attribute - setattr(cpda_form, "advanceDueAdjustment", advanceDueAdjustment) - setattr(cpda_form, "balanceAvailable", balanceAvailable) - setattr(cpda_form, "amountCheckedInPDA", amountCheckedInPDA) - cpda_form.save() - - #database - try: - cpda_form = CPDAReimbursementform.objects.get(id=form_id) - except CPDAReimbursementform.DoesNotExist: - return JsonResponse({"error": "CPDAReimbursementform object with the provided ID does not exist"}, status=404) - - - current_owner = get_current_file_owner(file_id) - - # if action value is 0 then forward the file - # if action value is 1 then reject the file - # if action value is 3 then approve the file - # otherwise archive the file - - if(action == '0'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = username_employee, receiver_designation = designation_employee,remarks = f"Forwarded by {current_owner} to {username_employee}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = username_employee, receiver_designation = designation_employee,remarks = f"Forwarded by {current_owner} to {username_employee}, Reason : {remark}", file_extra_JSON = "None") - messages.success(request, "File forwarded successfully") - elif(action == '1'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = cpda_form.name, receiver_designation = cpda_form.designation, remarks = f"Rejected by {current_owner}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = cpda_form.name, receiver_designation = cpda_form.designation, remarks = f"Rejected by {current_owner}, Reason : {remark}", file_extra_JSON = "None") - messages.success(request, "File rejected successfully") - elif(action == '2'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = cpda_form.name, receiver_designation = cpda_form.designation, remarks = f"Approved by {current_owner}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = cpda_form.name, receiver_designation = cpda_form.designation, remarks = f"Approved by {current_owner}, Reason : {remark}", file_extra_JSON = "None") - cpda_form.approved = True - cpda_form.approvedDate = timezone.now() - cpda_form.approved_by = current_owner - cpda_form.save() - messages.success(request, "File approved successfully") - else: - is_archived = archive_file(file_id=file_id) - if( is_archived ): - messages.error(request, "Error in file archived") - else: - messages.success(request, "Success in file archived") - - - return HttpResponse("Success") - else: - - return HttpResponse("Failure") - - - - -def file_handle_ltc(request): - if request.method == 'POST': - form_data2 = request.POST - form_data=request.POST.get('context') - action = form_data2.get('action') - - form_data=json.loads(form_data) - form_id = form_data['form_id'] - file_id = form_data['file_id'] - from_user = form_data['from_user'] - from_designation = form_data['from_designation'] - username_employee = form_data['username_employee'] - designation_employee = form_data['designation_employee'] - - remark = form_data['remark_id'] - #change - - - #database - try: - ltc_form = LTCform.objects.get(id=form_id) - except LTCform.DoesNotExist: - return JsonResponse({"error": "LTCform object with the provided ID does not exist"}, status=404) - - - ltc_form.save() - - - current_owner = get_current_file_owner(file_id) - - - # if action value is 0 then forward the file - # if action value is 1 then reject the file - # if action value is 3 then approve the file - # otherwise archive the file - - if(action == '0'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = username_employee, receiver_designation = designation_employee,remarks = f"Forwarded by {current_owner} to {username_employee}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = username_employee, receiver_designation = designation_employee,remarks = f"Forwarded by {current_owner} to {username_employee}, Reason : {remark}", file_extra_JSON = "None") - messages.success(request, "File forwarded successfully") - elif(action == '1'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = ltc_form.name, receiver_designation = ltc_form.designation, remarks = f"Rejected by {current_owner}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = ltc_form.name, receiver_designation = ltc_form.designation, remarks = f"Rejected by {current_owner}, Reason : {remark}", file_extra_JSON = "None") - messages.success(request, "File rejected successfully") - elif(action == '2'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = ltc_form.name, receiver_designation = ltc_form.designation, remarks = f"Approved by {current_owner}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = ltc_form.name, receiver_designation = ltc_form.designation, remarks = f"Approved by {current_owner}, Reason : {remark}", file_extra_JSON = "None") - ltc_form.approved = True - ltc_form.approvedDate = timezone.now() - ltc_form.approved_by = current_owner - ltc_form.save() - messages.success(request, "File approved successfully") - else: - is_archived = archive_file(file_id=file_id) - if( is_archived ): - messages.error(request, "Error in file archived") - else: - messages.success(request, "Success in file archived") - - - return HttpResponse("Success") - else: - - return HttpResponse("Failure") - -def file_handle_appraisal(request): - if request.method == 'POST': - form_data2 = request.POST - form_data=request.POST.get('context') - action = form_data2.get('action') - - form_data=json.loads(form_data) - form_id = form_data['form_id'] - file_id = form_data['file_id'] - from_user = form_data['from_user'] - from_designation = form_data['from_designation'] - username_employee = form_data['username_employee'] - designation_employee = form_data['designation_employee'] - - - remark = form_data['remark_id'] - try: - appraisal_form = Appraisalform.objects.get(id=form_id) - except Appraisalform.DoesNotExist: - return JsonResponse({"error": "Appraisalform object with the provided ID does not exist"}, status=404) - - - # Update the attribute - setattr(appraisal_form, "form_id", form_id) - - appraisal_form.save() - - current_owner = get_current_file_owner(file_id) - - #database - try: - appraisal_form = Appraisalform.objects.get(id=form_id) - except Appraisalform.DoesNotExist: - return JsonResponse({"error": "Appraisalform object with the provided ID does not exist"}, status=404) - - - # if action value is 0 then forward the file - # if action value is 1 then reject the file - # if action value is 3 then approve the file - # otherwise archive the file - - if(action == '0'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = username_employee, receiver_designation = designation_employee,remarks = f"Forwarded by {current_owner} to {username_employee}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = username_employee, receiver_designation = designation_employee,remarks = f"Forwarded by {current_owner} to {username_employee}, Reason : {remark}", file_extra_JSON = "None") - messages.success(request, "File forwarded successfully") - elif(action == '1'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = appraisal_form.name, receiver_designation = appraisal_form.designation, remarks = f"Rejected by {current_owner}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = appraisal_form.name, receiver_designation = appraisal_form.designation, remarks = f"Rejected by {current_owner}, Reason : {remark}", file_extra_JSON = "None") - messages.success(request, "File rejected successfully") - elif(action == '2'): - if(remark == ""): - track_id = forward_file(file_id = file_id, receiver = appraisal_form.name, receiver_designation = appraisal_form.designation, remarks = f"Approved by {current_owner}", file_extra_JSON = "None") - else: - track_id = forward_file(file_id = file_id, receiver = appraisal_form.name, receiver_designation = appraisal_form.designation, remarks = f"Approved by {current_owner}, Reason : {remark}", file_extra_JSON = "None") - appraisal_form.approved = True - appraisal_form.approvedDate = timezone.now() - appraisal_form.approved_by = current_owner - appraisal_form.save() - messages.success(request, "File approved successfully") - else: - is_archived = archive_file(file_id=file_id) - if( is_archived ): - messages.error(request, "Error in file archived") - else: - messages.success(request, "Success in file archived") - - - return HttpResponse("Success") - else: - - return HttpResponse("Failure") - - -def view_ltc_form(request, id): - ltc_request = get_object_or_404(LTCform, id=id) - - ltc_request = reverse_ltc_pre_processing(ltc_request) - - - context = { - 'ltc_request': ltc_request - } - return render(request,'hr2Module/view_ltc_form.html',context) - -def form_mangement_ltc(request): - if(request.method == "GET"): - username = "21BCS185" - designation = "hradmin" - inbox = view_inbox(username = username, designation = designation, src_module = "HR") - - - # Extract src_object_id values - src_object_ids = [item['src_object_id'] for item in inbox] - - ltc_requests = [] - - for src_object_id in src_object_ids: - ltc_request = get_object_or_404(LTCform, id=src_object_id) - ltc_requests.append(ltc_request) - - context= { - 'ltc_requests' : ltc_requests, - 'hr' : "1", - } - - - return render(request, 'hr2Module/ltc_form.html',context) - - -def form_mangement_ltc_hr(request,id): - uploader = "21BCS183" - uploader_designation = "student" - receiver = "21BCS181" - receiver_designation = "HOD" - src_module = "HR" - src_object_id = id, - file_extra_JSON = {"key": "value"} - - # Create a file representing the LTC form and send it to HR admin - file_id = create_file( - uploader=uploader, - uploader_designation=uploader_designation, - receiver=receiver, - receiver_designation=receiver_designation, - src_module=src_module, - src_object_id=src_object_id, - file_extra_JSON=file_extra_JSON, - attached_file=None # Attach any file if necessary - ) - - - messages.success(request, "Ltc form filled successfully") - - return HttpResponse("Sucess") - -def form_mangement_ltc_hod(request): - if(request.method == "GET"): - username = "21BCS181" - designation = "HOD" - inbox = view_inbox(username = username, designation = designation, src_module = "HR") - - - # Extract src_object_id values - src_object_ids = [item['src_object_id'] for item in inbox] - - ltc_requests = [] - - for src_object_id in src_object_ids: - ltc_request = get_object_or_404(LTCform, id=src_object_id) - ltc_requests.append(ltc_request) - - context= { - 'ltc_requests' : ltc_requests, - 'hr' : "1", - } - - - return render(request, 'hr2Module/ltc_form.html',context) - - - -@login_required(login_url='/accounts/login') -def dashboard(request): - user = request.user - - user_id = ExtraInfo.objects.get(user=user).user_id - context = {'user_id': user_id} - return render(request, 'hr2Module/dashboard.html',context) - - -# cpda form ----------------------------------------------------------- - -def reverse_cpda_pre_processing(data): - reversed_data = {} - - simple_keys = [ - 'name', 'designation', 'pfNo', 'purpose', 'amountRequired', 'advanceDueAdjustment', - 'submissionDate', - 'balanceAvailable', 'advanceAmountPDA' ,'amountCheckedInPDA', - ] - - - for key in simple_keys: - value = getattr(data, key) - reversed_data[key] = value if value != 'None' else '' - - return reversed_data - - -def cpda_form(request, id): - """ Views for edit details""" - try: - employee = ExtraInfo.objects.get(user__id=id) - except: - raise Http404("Employee does not exist! id doesnt exist") - - user_id = id - creator = User.objects.get(id = user_id) - - if(employee.user_type == 'faculty' or employee.user_type == 'staff' or employee.user_type == 'student' ): - template = 'hr2Module/cpda_form.html' - - if request.method == "POST": - try: - advanceAmountPDA = request.POST.get('advanceAmountPDA') - if advanceAmountPDA == "": - advanceAmountPDA = None - else: - advanceAmountPDA = Decimal(advanceAmountPDA) - - balanceAvailable = request.POST.get('balanceAvailable') - if balanceAvailable == "": - balanceAvailable = None - else: - balanceAvailable = Decimal(balanceAvailable) - - amountCheckedInPDA = request.POST.get('amountCheckedInPDA') - if amountCheckedInPDA == "": - amountCheckedInPDA = None - else: - amountCheckedInPDA = Decimal(amountCheckedInPDA) - - - form_2 = { - 'employeeId' : id, - 'name' : request.POST.get('name'), - 'designation' : request.POST.get('designation'), - 'pfNo' : request.POST.get('pfNo'), - 'purpose' : request.POST.get('purpose'), - 'amountRequired' : request.POST.get('amountRequired'), - 'advanceDueAdjustment' : request.POST.get('advanceDueAdjustment'), - 'submissionDate' : request.POST.get('submissionDate'), - 'balanceAvailable' : request.POST.get('balanceAvailable'), - 'advanceAmountPDA' : request.POST.get('advanceAmountPDA'), - 'amountCheckedInPDA' : request.POST.get('amountCheckedInPDA'), - 'created_by' : creator, - } - - cpda_form = CPDAAdvanceform.objects.create( - employeeId = id, - name = request.POST.get('name'), - designation = request.POST.get('designation'), - pfNo = request.POST.get('pfNo'), - purpose = request.POST.get('purpose'), - amountRequired = request.POST.get('amountRequired'), - advanceDueAdjustment = request.POST.get('advanceDueAdjustment'), - submissionDate = request.POST.get('submissionDate'), - balanceAvailable = request.POST.get('balanceAvailable'), - advanceAmountPDA = request.POST.get('advanceAmountPDA'), - amountCheckedInPDA = request.POST.get('amountCheckedInPDA'), - created_by=creator, - - ) - - - uploader = employee.user - uploader_designation = 'Assistant Professor' - - get_designation = get_designation_by_user_id(employee.user) - if(get_designation): - uploader_designation = get_designation - - receiver = request.POST.get('username_employee') - receiver_designation = request.POST.get('designation_employee') - src_module = "HR" #dikkat - src_object_id = str(cpda_form.id) - file_extra_JSON = {"type": "CPDAAdvance"} - - # Create a file representing the CPDA form - file_id = create_file( - uploader=uploader, - uploader_designation=uploader_designation, - receiver=receiver, - receiver_designation=receiver_designation, - src_module=src_module, - src_object_id=src_object_id, - file_extra_JSON=file_extra_JSON, - attached_file=None # Attach any file if necessary - ) - - - - messages.success(request, "CPDA form filled successfully") - - return redirect(request.path_info) - - except Exception as e: - messages.warning(request, "Fill not correctly") - context = {'employee': employee} - return render(request, template, context) - - cpda_requests = CPDAAdvanceform.objects.filter(employeeId=id) - - username = employee.user - uploader_designation = 'Assistant Professor' - - - designation = get_designation_by_user_id(employee.user) - if(designation): - uploader_designation = designation - - - inbox = view_inbox(username = username, designation = uploader_designation, src_module = "HR") - - archived_files = view_archived(username = username, designation = uploader_designation, src_module = "HR") - - filtered_inbox = [] - for i in inbox: - item = i.get('file_extra_JSON', {}) - if item.get('type') == 'CPDAAdvance': - filtered_inbox.append(i) - - filtered_archived_files = [] - for i in archived_files: - item = i.get('file_extra_JSON', {}) - if item.get('type') == 'CPDAAdvance': - filtered_archived_files.append(i) - - context = {'employee': employee, 'cpda_requests': cpda_requests, 'inbox': filtered_inbox , 'designation':designation, 'archived_files': filtered_archived_files,'user_id':user_id} - - - messages.success(request, "CPDA form filled successfully!") - return render(request, template, context) - else: - return render(request, 'hr2Module/edit.html') - - -def form_view_cpda(request , id): - cpda_request = get_object_or_404(CPDAAdvanceform, id=id) - user_id = cpda_request.created_by.id - from_user = request.GET.get('param1') - from_designation = request.GET.get('param2') - file_id = request.GET.get('param3') - - - - template = 'hr2Module/view_cpda_form.html' - cpda_request = reverse_cpda_pre_processing(cpda_request) - - context = {'cpda_request' : cpda_request , "button" : 1 , "file_id" : file_id, "from_user" :from_user , "from_designation" : from_designation,"id":id,"user_id":user_id} - - return render(request , template , context) - - -def view_cpda_form(request, id): - cpda_request = get_object_or_404(CPDAAdvanceform, id=id) - - cpda_request = reverse_cpda_pre_processing(cpda_request) - - - context = { - 'cpda_request': cpda_request - } - return render(request,'hr2Module/view_cpda_form.html',context) - - -def form_mangement_cpda(request): - if(request.method == "GET"): - username = "21BCS185" - designation = "hradmin" - inbox = view_inbox(username = username, designation = designation, src_module = "HR") - - - # Extract src_object_id values - src_object_ids = [item['src_object_id'] for item in inbox] - - - cpda_requests = [] - - for src_object_id in src_object_ids: - cpda_request = get_object_or_404(CPDAAdvanceform, id=src_object_id) - cpda_requests.append(cpda_request) - - context= { - 'cpda_requests' : cpda_requests, - 'hr' : "1", - } - - - return render(request, 'hr2Module/cpda_form.html',context) - -def form_mangement_cpda_hr(request,id): - uploader = "21BCS183" - uploader_designation = "student" - receiver = "21BCS181" - receiver_designation = "HOD" - src_module = "HR" - src_object_id = id, - file_extra_JSON = {"key": "value"} - - # Create a file representing the CPDA form and send it to HR admin - file_id = create_file( - uploader=uploader, - uploader_designation=uploader_designation, - receiver=receiver, - receiver_designation=receiver_designation, - src_module=src_module, - src_object_id=src_object_id, - file_extra_JSON=file_extra_JSON, - attached_file=None # Attach any file if necessary - ) - - - messages.success(request, "CPda form filled successfully") - - - return HttpResponse("Success") - - -def form_mangement_cpda_hod(request): - if(request.method == "GET"): - username = "21BCS181" - designation = "HOD" - inbox = view_inbox(username = username, designation = designation, src_module = "HR") - - - # Extract src_object_id values - src_object_ids = [item['src_object_id'] for item in inbox] - - - cpda_requests = [] - - for src_object_id in src_object_ids: - cpda_request = get_object_or_404(CPDAAdvanceform, id=src_object_id) - cpda_requests.append(cpda_request) - - context= { - 'cpda_requests' : cpda_requests, - 'hr' : "1", - } - - - return render(request, 'hr2Module/cpda_form.html',context) - - -# Leave form ------------------------------------------------------------- - -def reverse_leave_pre_processing(data): - reversed_data = {} - - # Copying over simple key-value pairs - simple_keys = [ - 'name', 'designation', 'submissionDate', 'pfNo', 'departmentInfo', 'natureOfLeave', - 'leaveStartDate', 'leaveEndDate', 'purposeOfLeave', 'addressDuringLeave', 'academicResponsibility', - 'addministrativeResponsibiltyAssigned' - ] - - - for key in simple_keys: - value = getattr(data, key) - reversed_data[key] = value if value != 'None' else '' - - return reversed_data - - -def leave_form(request, id): - """ Views for edit details""" - try: - employee = ExtraInfo.objects.get(user__id=id) - except: - raise Http404("Employee does not exist! id doesnt exist") - - user_id = id - creator = User.objects.get(id = user_id) - - if(employee.user_type == 'faculty' or employee.user_type == 'student' or employee.user_type == 'staff'): - template = 'hr2Module/leave_form.html' - - if request.method == "POST": - try: - - - form_3 = { - 'employeeId' : id, - 'name' : request.POST.get('name'), - 'designation' : request.POST.get('designation'), - 'submissionDate' : request.POST.get('submissionDate'), - 'pfNo' : request.POST.get('pfNo'), - 'departmentInfo' : request.POST.get('departmentInfo'), - 'natureOfLeave' : request.POST.get('natureOfLeave'), - 'leaveStartDate' : request.POST.get('leaveStartDate'), - 'leaveEndDate' : request.POST.get('leaveEndDate'), - 'purposeOfLeave' : request.POST.get('purposeOfLeave'), - 'addressDuringLeave' : request.POST.get('addressDuringLeave'), - 'academicResponsibility' : request.POST.get('academicResponsibility'), - 'addministrativeResponsibiltyAssigned' : request.POST.get('addministrativeResponsibiltyAssigned'), - 'created_by' : creator, - } - - leave_form = LeaveForm.objects.create( - employeeId = id, - name = request.POST.get('name'), - designation = request.POST.get('designation'), - submissionDate = request.POST.get('submissionDate'), - pfNo = request.POST.get('pfNo'), - departmentInfo = request.POST.get('departmentInfo'), - leaveStartDate = request.POST.get('leaveStartDate'), - leaveEndDate = request.POST.get('leaveEndDate'), - natureOfLeave = request.POST.get('natureOfLeave'), - purposeOfLeave = request.POST.get('purposeOfLeave'), - addressDuringLeave = request.POST.get('addressDuringLeave'), - academicResponsibility = request.POST.get('academicResponsibility'), - addministrativeResponsibiltyAssigned = request.POST.get('addministrativeResponsibiltyAssigned'), - created_by=creator, - ) - - - uploader = employee.user - uploader_designation = 'Assistant Professor' - - - get_designation = get_designation_by_user_id(employee.user) - if(get_designation): - uploader_designation = get_designation - - receiver = request.POST.get('username_employee') - receiver_designation = request.POST.get('designation_employee') - src_module = "HR" - src_object_id = str(leave_form.id) - file_extra_JSON = {"type": "Leave"} - - - # Create a file representing the CPDA form - file_id = create_file( - uploader=uploader, - uploader_designation=uploader_designation, - receiver=receiver, - receiver_designation=receiver_designation, - src_module=src_module, - src_object_id=src_object_id, - file_extra_JSON=file_extra_JSON, - attached_file=None # Attach any file if necessary - ) - - - messages.success(request, "Leave form filled successfully") - - return redirect(request.path_info) - - except Exception as e: - messages.warning(request, "Fill not correctly") - context = {'employee': employee} - return render(request, template, context) - - # Query all Leave requests - leave_requests = LeaveForm.objects.filter(employeeId=id) - - username = employee.user - uploader_designation = 'Assistant Professor' - - designation = get_designation_by_user_id(employee.user) - if(designation): - uploader_designation = designation - - - inbox = view_inbox(username = username, designation = uploader_designation, src_module = "HR") - - archived_files = view_archived(username = username, designation = uploader_designation, src_module = "HR") - - filtered_inbox = [] - for i in inbox: - item = i.get('file_extra_JSON', {}) - if item.get('type') == 'Leave': - filtered_inbox.append(i) - - filtered_archived_files = [] - for i in archived_files: - item = i.get('file_extra_JSON', {}) - if item.get('type') == 'Leave': - filtered_archived_files.append(i) - - - - context = {'employee': employee, 'leave_requests': leave_requests, 'inbox': filtered_inbox , 'designation':designation, 'archived_files': filtered_archived_files,'user_id':user_id} - - messages.success(request, "Leave form filled successfully!") - return render(request, template, context) - else: - return render(request, 'hr2Module/edit.html') - - - -def form_view_leave(request , id): - - leave_request = get_object_or_404(LeaveForm, id=id) - user_id = leave_request.created_by.id - from_user = request.GET.get('param1') - from_designation = request.GET.get('param2') - file_id = request.GET.get('param3') - - - template = 'hr2Module/view_leave_form.html' - leave_request = reverse_leave_pre_processing(leave_request) - - context = {'leave_request' : leave_request , "button" : 1 , "file_id" : file_id, "from_user" :from_user , "from_designation" : from_designation, "id" : id,"user_id":user_id} - - return render(request , template , context) - -# ek or bna lena -def view_leave_form(request, id): - leave_request = get_object_or_404(LeaveForm, id=id) - - - - leave_request = reverse_leave_pre_processing(leave_request) - - - context = { - 'leave_request': leave_request - } - return render(request,'hr2Module/view_leave_form.html',context) - - -def form_mangement_leave(request): - if(request.method == "GET"): - username = "21BCS185" - designation = "hradmin" - inbox = view_inbox(username = username, designation = designation, src_module = "HR") - - - # Extract src_object_id values - src_object_ids = [item['src_object_id'] for item in inbox] - - leave_requests = [] - - for src_object_id in src_object_ids: - leave_request = get_object_or_404(LeaveForm, id=src_object_id) - leave_requests.append(leave_request) - - context= { - 'leave_requests' : leave_requests, - 'hr' : "1", - } - - - return render(request, 'hr2Module/leave_form.html',context) - - -def form_mangement_leave_hr(request,id): - uploader = "21BCS183" - uploader_designation = "student" - receiver = "21BCS181" - receiver_designation = "HOD" - src_module = "HR" - src_object_id = id, - file_extra_JSON = {"key": "value"} - - # Create a file representing the Leave form and send it to HR admin - file_id = create_file( - uploader=uploader, - uploader_designation=uploader_designation, - receiver=receiver, - receiver_designation=receiver_designation, - src_module=src_module, - src_object_id=src_object_id, - file_extra_JSON=file_extra_JSON, - attached_file=None # Attach any file if necessary - ) - - - messages.success(request, "Leave form filled successfully") - - return HttpResponse("Sucess") - -def form_mangement_leave_hod(request): - if(request.method == "GET"): - username = "21BCS181" - designation = "HOD" - inbox = view_inbox(username = username, designation = designation, src_module = "HR") - - - # Extract src_object_id values - src_object_ids = [item['src_object_id'] for item in inbox] - - - leave_requests = [] - - for src_object_id in src_object_ids: - leave_request = get_object_or_404(LeaveForm, id=src_object_id) - leave_requests.append(leave_request) - - context= { - 'leave_requests' : leave_requests, - 'hr' : "1", - } - - - return render(request, 'hr2Module/leave_form.html',context) - - - -def appraisal_form(request, id): - """ Views for edit details""" - try: - employee = ExtraInfo.objects.get(user__id=id) - except: - raise Http404("Employee does not exist! id doesnt exist") - - user_id = id - creator = User.objects.get(id = user_id) - - if(employee.user_type == 'faculty' or employee.user_type == 'staff' or employee.user_type == 'student'): - template = 'hr2Module/appraisal_form.html' - - if request.method == "POST": - try: - - data = appraisal_pre_processing(request) - - - form_4 = { - 'employeeId': id, - 'name': request.POST.get('name'), - 'designation': request.POST.get('designation'), - 'disciplineInfo': request.POST.get('disciplineInfo'), - 'specificFieldOfKnowledge': request.POST.get('specificFieldOfKnowledge'), - 'currentResearchInterests': request.POST.get('currentResearchInterests'), - 'coursesTaught': data['coursesTaught'], - 'newCoursesIntroduced': data['newCoursesIntroduced'], - 'newCoursesDeveloped': data['newCoursesDeveloped'], - 'otherInstructionalTasks': request.POST.get('otherInstructionalTasks'), - 'thesisSupervision': data['thesisSupervision'], - 'sponsoredReseachProjects': data['sponsoredReseachProjects'], - 'otherResearchElement': request.POST.get('otherResearchElement'), - 'publication': request.POST.get('publication'), - 'referredConference': request.POST.get('referredConference'), - 'conferenceOrganised': request.POST.get('conferenceOrganised'), - 'membership': request.POST.get('membership'), - 'honours ' : request.POST.get('honours'), - 'editorOfPublications': request.POST.get('editorOfPublications'), - 'expertLectureDelivered': request.POST.get('expertLectureDelivered'), - 'membershipOfBOS': request.POST.get('membershipOfBOS'), - 'otherExtensionTasks': request.POST.get('otherExtensionTasks'), - 'administrativeAssignment': request.POST.get('administrativeAssignment'), - 'serviceToInstitute': request.POST.get('serviceToInstitute'), - 'otherContribution': request.POST.get('otherContribution'), - 'performanceComments' : request.POST.get('performanceComments'), - 'submissionDate' : request.POST.get('submissionDate'), - 'approved' : request.POST.get('approved'), - 'approvedDate' : request.POST.get('approvedDate'), - 'created_by' : creator, - - } - - - appraisal_form = Appraisalform.objects.create( - employeeId= id, - name= request.POST.get('name'), - designation= request.POST.get('designation'), - disciplineInfo= request.POST.get('disciplineInfo'), - specificFieldOfKnowledge= request.POST.get('specificFieldOfKnowledge'), - currentResearchInterests= request.POST.get('currentResearchInterests'), - coursesTaught= data['coursesTaught'], - newCoursesIntroduced= data['newCoursesIntroduced'], - newCoursesDeveloped= data['newCoursesDeveloped'], - otherInstructionalTasks= request.POST.get('otherInstructionalTasks'), - thesisSupervision= data['thesisSupervision'], - sponsoredReseachProjects= data['sponsoredReseachProjects'], - otherResearchElement= request.POST.get('otherResearchElement'), - publication= request.POST.get('publication'), - referredConference= request.POST.get('referredConference'), - conferenceOrganised= request.POST.get('conferenceOrganised'), - membership= request.POST.get('membership'), - honours = request.POST.get('honours'), - editorOfPublications= request.POST.get('editorOfPublications'), - expertLectureDelivered= request.POST.get('expertLectureDelivered'), - membershipOfBOS= request.POST.get('membershipOfBOS'), - otherExtensionTasks= request.POST.get('otherExtensionTasks'), - administrativeAssignment= request.POST.get('administrativeAssignment'), - serviceToInstitute= request.POST.get('serviceToInstitute'), - otherContribution= request.POST.get('otherContribution'), - performanceComments = request.POST.get('performanceComments'), - submissionDate = request.POST.get('submissionDate'), - approved = request.POST.get('approved'), - approvedDate = request.POST.get('approvedDate'), - created_by=creator, - - - ) - - uploader = employee.user - uploader_designation = 'Assistant Professor' - - get_designation = get_designation_by_user_id(employee.user) - if(get_designation): - uploader_designation = get_designation - - receiver = request.POST.get('username_employee') - receiver_designation = request.POST.get('designation_employee') - src_module = "HR" - src_object_id = str(appraisal_form.id) - file_extra_JSON = {"type": "Appraisal"} - - - # Create a file representing the AppraisL form and send it to HR admin - file_id = create_file( - uploader=uploader, - uploader_designation=uploader_designation, - receiver=receiver, - receiver_designation=receiver_designation, - src_module=src_module, - src_object_id=src_object_id, - file_extra_JSON=file_extra_JSON, - attached_file=None # Attach any file if necessary - ) - - - messages.success(request, "Appraisal form filled successfully") - - return redirect(request.path_info) - - except Exception as e: - messages.warning(request, "Fill not correctly") - context = {'employee': employee} - return render(request, template, context) - - - - appraisal_requests = Appraisalform.objects.filter(employeeId=id) - - username = employee.user - uploader_designation = 'Assistant Professor' - - - - designation = get_designation_by_user_id(employee.user) - if(designation): - uploader_designation = designation - - - inbox = view_inbox(username = username, designation = uploader_designation, src_module = "HR") - - archived_files = view_archived(username = username, designation = uploader_designation, src_module = "HR") - - filtered_inbox = [] - for i in inbox: - item = i.get('file_extra_JSON', {}) - if item.get('type') == 'Appraisal': - filtered_inbox.append(i) - - filtered_archived_files = [] - for i in archived_files: - item = i.get('file_extra_JSON', {}) - if item.get('type') == 'Appraisal': - filtered_archived_files.append(i) - - - context = {'employee': employee, 'appraisal_requests': appraisal_requests, 'inbox': filtered_inbox , 'designation':designation, 'archived_files': filtered_archived_files,'user_id':user_id} - - messages.success(request, "Appraisal form filled successfully!") - return render(request, template, context) - else: - return render(request, 'hr2Module/edit.html') - - - -def form_view_appraisal(request , id): - appraisal_request = get_object_or_404(Appraisalform, id=id) - user_id = appraisal_request.created_by.id - from_user = request.GET.get('param1') - from_designation = request.GET.get('param2') - file_id = request.GET.get('param3') - - - template = 'hr2Module/view_appraisal_form.html' - appraisal_request = reverse_appraisal_pre_processing(appraisal_request) - - context = {'appraisal_request' : appraisal_request , "button" : 1 , "file_id" : file_id, "from_user" :from_user , "from_designation" : from_designation,"id":id,"user_id":user_id} - - return render(request , template , context) - - -def view_appraisal_form(request, id): - appraisal_request = get_object_or_404(Appraisalform, id=id) - - - appraisal_request = reverse_appraisal_pre_processing(appraisal_request) - - context = { - 'appraisal_request': appraisal_request - } - return render(request,'hr2Module/view_appraisal_form.html',context) - - - -def form_mangement_appraisal(request): - if(request.method == "GET"): - username = "21BCS185" - designation = "hradmin" - inbox = view_inbox(username = username, designation = designation, src_module = "HR") - - - src_object_ids = [item['src_object_id'] for item in inbox] - - appraisal_requests = [] - - for src_object_id in src_object_ids: - appraisal_request = get_object_or_404(Appraisalform, id=src_object_id) - appraisal_requests.append(appraisal_request) - - context= { - 'appraisal_requests' : appraisal_requests, - 'hr' : "1", - } - - - return render(request, 'hr2Module/appraisal_form.html',context) - - -def form_mangement_appraisal_hr(request,id): - uploader = "21BCS183" - uploader_designation = "student" - receiver = "21BCS181" - receiver_designation = "HOD" - src_module = "HR" - src_object_id = id, - file_extra_JSON = {"key": "value"} - - # Create a file representing the Appraisal form and send it to HR admin - file_id = create_file( - uploader=uploader, - uploader_designation=uploader_designation, - receiver=receiver, - receiver_designation=receiver_designation, - src_module=src_module, - src_object_id=src_object_id, - file_extra_JSON=file_extra_JSON, - attached_file=None # Attach any file if necessary - ) - - - messages.success(request, "Appraisal form filled successfully") - - return HttpResponse("Sucess") - - - -def appraisal_pre_processing(request): - data = {} - - - coursesTaught = "" - - for i in range(1,3): - for j in range(1,8): - key_is = f'info_{i}_{j}' - - if(request.POST.get(key_is) == ""): - coursesTaught = coursesTaught + 'None' + ',' - else: - coursesTaught = coursesTaught + request.POST.get(key_is) + ',' - - data['coursesTaught'] = coursesTaught.rstrip(',') - - newCoursesIntroduced = "" - - for i in range(3,5): - for j in range(1,4): - key_is = f'info_{i}_{j}' - - if(request.POST.get(key_is) == ""): - newCoursesIntroduced = newCoursesIntroduced + 'None' + ',' - else: - newCoursesIntroduced = newCoursesIntroduced + request.POST.get(key_is) + ',' - - data['newCoursesIntroduced'] = newCoursesIntroduced.rstrip(',') - - - newCoursesDeveloped = "" - - for i in range(5,7): - for j in range(1,5): - key_is = f'info_{i}_{j}' - - if(request.POST.get(key_is) == ""): - newCoursesDeveloped = newCoursesDeveloped + 'None' + ',' - else: - newCoursesDeveloped = newCoursesDeveloped + request.POST.get(key_is) + ',' - - data['newCoursesDeveloped'] = newCoursesDeveloped.rstrip(',') - - thesisSupervision = "" - - for i in range(7,9): - for j in range(1,6): - key_is = f'info_{i}_{j}' - - if(request.POST.get(key_is) == ""): - thesisSupervision = thesisSupervision + 'None' + ',' - else: - thesisSupervision = thesisSupervision + request.POST.get(key_is) + ',' - - data['thesisSupervision'] = thesisSupervision.rstrip(',') - - - - sponsoredReseachProjects = "" - - for i in range(9,10): - for j in range(1,8): - key_is = f'info_{i}_{j}' - - if(request.POST.get(key_is) == ""): - sponsoredReseachProjects = sponsoredReseachProjects + 'None' + ',' - else: - sponsoredReseachProjects = sponsoredReseachProjects + request.POST.get(key_is) + ',' - - data['sponsoredReseachProjects'] = sponsoredReseachProjects.rstrip(',') - - - return data - - - - -def reverse_appraisal_pre_processing(data): - reversed_data = {} - - # Copying over simple key-value pairs - simple_keys = [ - 'name', 'designation', 'disciplineInfo', 'specificFieldOfKnowledge', 'designation', 'currentResearchInterests', - 'otherInstructionalTasks', 'otherResearchElement', 'publication', 'referredConference', - 'conferenceOrganised', 'membership', 'honours', 'editorOfPublications', - 'expertLectureDelivered', 'membershipOfBOS', 'otherExtensionTasks', - 'administrativeAssignment', 'serviceToInstitute', 'otherContribution', 'performanceComments', - 'submissionDate' - ] - - - for key in simple_keys: - value = getattr(data, key) - reversed_data[key] = value if value != 'None' else '' - - courses_taught = getattr(data,'coursesTaught').split(',') - for index, value in enumerate(courses_taught): - courses_taught[index] = value if value != 'None' else '' - - reversed_data['info_1_1'] = courses_taught[0] - reversed_data['info_1_2'] = courses_taught[1] - reversed_data['info_1_3'] = courses_taught[2] - reversed_data['info_1_4'] = courses_taught[3] - reversed_data['info_1_5'] = courses_taught[4] - reversed_data['info_1_6'] = courses_taught[5] - reversed_data['info_1_7'] = courses_taught[6] - reversed_data['info_2_1'] = courses_taught[7] - reversed_data['info_2_2'] = courses_taught[8] - reversed_data['info_2_3'] = courses_taught[9] - reversed_data['info_2_4'] = courses_taught[10] - reversed_data['info_2_5'] = courses_taught[11] - reversed_data['info_2_6'] = courses_taught[12] - reversed_data['info_2_7'] = courses_taught[13] - - # # Reversing details_of_dependents - new_courses_introduced = getattr(data,'newCoursesIntroduced').split(',') - for i in range(3, 5): - for j in range(1, 4): - key = f'info_{i}_{j}' - value = new_courses_introduced.pop(0) - reversed_data[key] = value if value != 'None' else '' - - - - newCoursesDeveloped = getattr(data,'newCoursesDeveloped').split(',') - for i in range(5, 7): - for j in range(1, 5): - key = f'info_{i}_{j}' - value = newCoursesDeveloped.pop(0) - reversed_data[key] = value if value != 'None' else '' - - - - thesis_reasearch = getattr(data,'otherResearchElement').split(',') - for i in range(7, 9): - for j in range(1, 6): - key = f'info_{i}_{j}' - if thesis_reasearch: - value = thesis_reasearch.pop() - else: - # Handle the case where the list is empty - print("The list is empty, cannot pop from it.") - # value = thesis_reasearch.pop(0) - reversed_data[key] = value if value != 'None' else '' - - - - sponsored_research = getattr(data,'sponsoredReseachProjects').split(',') - for i in range(9, 10): - for j in range(1, 8): - key = f'info_{i}_{j}' - value = sponsored_research.pop(0) - reversed_data[key] = value if value != 'None' else '' - - return reversed_data - - - -def reverse_cpda_reimbursement_pre_processing(data): - reversed_data = {} - - simple_keys = [ - 'name', 'designation', 'pfNo', 'purpose', 'advanceTaken', 'adjustmentSubmitted', - 'submissionDate', - 'balanceAvailable', 'advanceDueAdjustment', 'amountCheckedInPDA', - ] - - - for key in simple_keys: - value = getattr(data, key) - reversed_data[key] = value if value != 'None' else '' - - return reversed_data - - -def cpda_reimbursement_form(request, id): - """ Views for edit details""" - try: - employee = ExtraInfo.objects.get(user__id=id) - except: - raise Http404("Employee does not exist! id doesnt exist") - - user_id = id - creator = User.objects.get(id = user_id) - - if(employee.user_type == 'faculty' or employee.user_type == 'staff' or employee.user_type == 'student' ): - template = 'hr2Module/cpda_reimbursement_form.html' - - if request.method == "POST": - try: - - form_2 = { - 'employeeId' : id, - 'name' : request.POST.get('name'), - 'designation' : request.POST.get('designation'), - 'pfNo' : request.POST.get('pfNo'), - 'purpose' : request.POST.get('purpose'), - 'advanceTaken' : request.POST.get('advanceTaken'), - 'advanceDueAdjustment' : request.POST.get('advanceDueAdjustment'), - 'submissionDate' : request.POST.get('submissionDate'), - 'balanceAvailable' : request.POST.get('balanceAvailable'), - 'adjustmentSubmitted' : request.POST.get('adjustmentSubmitted'), - 'amountCheckedInPDA' : request.POST.get('amountCheckedInPDA'), - 'created_by' : creator, - } - - cpda_form = CPDAReimbursementform.objects.create( - employeeId = id, - name = request.POST.get('name'), - designation = request.POST.get('designation'), - pfNo = request.POST.get('pfNo'), - purpose = request.POST.get('purpose'), - advanceTaken = request.POST.get('advanceTaken'), - advanceDueAdjustment = request.POST.get('advanceDueAdjustment'), - submissionDate = request.POST.get('submissionDate'), - balanceAvailable = request.POST.get('balanceAvailable'), - adjustmentSubmitted = request.POST.get('adjustmentSubmitted'), - amountCheckedInPDA = request.POST.get('amountCheckedInPDA'), - created_by=creator, - - ) - - - uploader = employee.user - uploader_designation = 'Assistant Professor' - - get_designation = get_designation_by_user_id(employee.user) - if(get_designation): - uploader_designation = get_designation - - receiver = request.POST.get('username_employee') - receiver_designation = request.POST.get('designation_employee') - src_module = "HR" - src_object_id = str(cpda_form.id) - file_extra_JSON = {"type": "CPDAReimbursement"} - - # Create a file representing the CPDA form - file_id = create_file( - uploader=uploader, - uploader_designation=uploader_designation, - receiver=receiver, - receiver_designation=receiver_designation, - src_module=src_module, - src_object_id=src_object_id, - file_extra_JSON=file_extra_JSON, - attached_file=None # Attach any file if necessary - ) - - messages.success(request, "cpdareimbursement form filled successfully") - - return redirect(request.path_info) - - except Exception as e: - messages.warning(request, "Fill not correctly") - context = {'employee': employee} - return render(request, template, context) - - cpda_reimbursement_requests = CPDAReimbursementform.objects.filter(employeeId=id) - - username = employee.user - uploader_designation = 'Assistant Professor' - - - designation = get_designation_by_user_id(employee.user) - if(designation): - uploader_designation = designation - - - inbox = view_inbox(username = username, designation = uploader_designation, src_module = "HR") - - archived_files = view_archived(username = username, designation = uploader_designation, src_module = "HR") - - filtered_inbox = [] - for i in inbox: - item = i.get('file_extra_JSON', {}) - if item.get('type') == 'CPDAReimbursement': - filtered_inbox.append(i) - - filtered_archived_files = [] - for i in archived_files: - item = i.get('file_extra_JSON', {}) - if item.get('type') == 'CPDAReimbursement': - filtered_archived_files.append(i) - - - context = {'employee': employee, 'cpda_reimbursement_requests': cpda_reimbursement_requests, 'inbox': filtered_inbox , 'designation':designation, 'archived_files': filtered_archived_files,'user_id':user_id} - - - messages.success(request, "cpdareimbursement form filled successfully!") - return render(request, template, context) - else: - return render(request, 'hr2Module/edit.html') - - - - -def form_view_cpda_reimbursement(request , id): - cpda_reimbursement_request = get_object_or_404(CPDAReimbursementform, id=id) - user_id = cpda_reimbursement_request.created_by.id - # isko recheck krna h - from_user = request.GET.get('param1') - from_designation = request.GET.get('param2') - file_id = request.GET.get('param3') - - template = 'hr2Module/view_cpda_reimbursement_form.html' - cpda_reimbursement_request = reverse_cpda_reimbursement_pre_processing(cpda_reimbursement_request) - - context = {'cpda_reimbursement_request' : cpda_reimbursement_request , "button" : 1 , "file_id" : file_id, "from_user" :from_user , "from_designation" : from_designation,"id":id,"user_id":user_id} - - return render(request , template , context) - - -def view_cpda_reimbursement_form(request, id): - cpda_reimbursement_request = get_object_or_404(CPDAReimbursementform, id=id) - - cpda_reimbursement_request = reverse_cpda_reimbursement_pre_processing(cpda_reimbursement_request) - - context = { - 'cpda_reimbursement_request': cpda_reimbursement_request - } - return render(request,'hr2Module/view_cpda_reimbursement_form.html',context) - - - -def form_mangement_cpda_reimbursement(request): - if(request.method == "GET"): - username = "21BCS185" - designation = "hradmin" - inbox = view_inbox(username = username, designation = designation, src_module = "HR") - - - # Extract src_object_id values - src_object_ids = [item['src_object_id'] for item in inbox] - - - cpda_reimbursement_requests = [] - - for src_object_id in src_object_ids: - cpda_reimbursement_request = get_object_or_404(CPDAReimbursementform, id=src_object_id) - cpda_reimbursement_requests.append(cpda_reimbursement_request) - - context= { - 'cpda_reimbursement_requests' : cpda_reimbursement_requests, - 'hr' : "1", - } - - - return render(request, 'hr2Module/cpda_reimbursement_form.html',context) - -def form_mangement_cpda_reimbursement_hr(request,id): - uploader = "21BCS183" - uploader_designation = "student" - receiver = "21BCS181" - receiver_designation = "HOD" - src_module = "HR" - src_object_id = id, - file_extra_JSON = {"key": "value"} - - # Create a file representing the CPDA form and send it to HR admin - file_id = create_file( - uploader=uploader, - uploader_designation=uploader_designation, - receiver=receiver, - receiver_designation=receiver_designation, - src_module=src_module, - src_object_id=src_object_id, - file_extra_JSON=file_extra_JSON, - attached_file=None # Attach any file if necessary - ) - - - messages.success(request, "CPda form filled successfully") - - - return HttpResponse("Success") - - -def form_mangement_cpda_reimbursement_hod(request): - if(request.method == "GET"): - username = "21BCS181" - designation = "HOD" - inbox = view_inbox(username = username, designation = designation, src_module = "HR") - - - # Extract src_object_id values - src_object_ids = [item['src_object_id'] for item in inbox] - - - cpda_reimbursement_requests = [] - - for src_object_id in src_object_ids: - cpda_reimbursement_request = get_object_or_404(CPDAReimbursementform, id=src_object_id) - cpda_reimbursement_requests.append(cpda_reimbursement_request) - - context= { - 'cpda_reimbursement_requests' : cpda_reimbursement_requests, - 'hr' : "1", - } - - - return render(request, 'hr2Module/cpda_reimbursement_form.html',context) - - -def getform(request): - form_type = request.GET.get("type") - id = request.GET.get("id") - - if form_type == "LTC": - try: - forms = LTCform.objects.filter(created_by=id) - - form_data = [] - for form in forms: - form_data.append({ - 'id': form.id, - 'name': form.name, - 'designation': form.designation, - 'submissionDate': form.submissionDate.strftime("%Y-%m-%d") if form.submissionDate else None, - 'is_approved' : form.approved, - - }) - - return JsonResponse(form_data, safe=False) # Return JSON response - except LTCform.DoesNotExist: - return JsonResponse({"message": "No LTC forms found."}, status=404) - -def getformcpdaAdvance(request): - form_type = request.GET.get("type") - id = request.GET.get("id") - - if form_type == "CPDAAdvance": - try: - forms = CPDAAdvanceform.objects.filter(created_by=id) - form_data = [] - for form in forms: - form_data.append({ - 'id': form.id, - 'name': form.name, - 'designation': form.designation, - 'submissionDate': form.submissionDate.strftime("%Y-%m-%d") if form.submissionDate else None, - 'is_approved' : form.approved, - }) - - return JsonResponse(form_data, safe=False) # Return JSON response - except CPDAAdvanceform.DoesNotExist: - return JsonResponse({"message": "No CPDAAdvance forms found."}, status=404) - - -def getformLeave(request): - form_type = request.GET.get("type") - id = request.GET.get("id") - - if form_type == "Leave": - try: - forms = LeaveForm.objects.filter(created_by=id) - form_data = [] - for form in forms: - form_data.append({ - 'id': form.id, - 'name': form.name, - 'designation': form.designation, - 'submissionDate': form.submissionDate.strftime("%Y-%m-%d") if form.submissionDate else None, - 'is_approved' : form.approved, - # Add other fields as needed - }) - - return JsonResponse(form_data, safe=False) # Return JSON response - except LeaveForm.DoesNotExist: - return JsonResponse({"message": "No Leave forms found."}, status=404) - - -def getformAppraisal(request): - form_type = request.GET.get("type") - id = request.GET.get("id") - - if form_type == "Appraisal": - try: - forms = Appraisalform.objects.filter(created_by=id) - form_data = [] - for form in forms: - form_data.append({ - 'id': form.id, - 'name': form.name, - 'designation': form.designation, - 'submissionDate': form.submissionDate.strftime("%Y-%m-%d") if form.submissionDate else None, - 'is_approved' : form.approved, - }) - - return JsonResponse(form_data, safe=False) # Return JSON response - except Appraisalform.DoesNotExist: - return JsonResponse({"message": "No Appraisal forms found."}, status=404) - - - -def getformcpdaReimbursement(request): - form_type = request.GET.get("type") - id = request.GET.get("id") - - if form_type == "CPDAReimbursement": - try: - forms = CPDAReimbursementform.objects.filter(created_by=id) - form_data = [] - for form in forms: - form_data.append({ - 'id': form.id, - 'name': form.name, - 'designation': form.designation, - 'submissionDate': form.submissionDate.strftime("%Y-%m-%d") if form.submissionDate else None, - 'is_approved' : form.approved, - # Add other fields as needed - }) - - return JsonResponse(form_data, safe=False) # Return JSON response - except CPDAReimbursementform.DoesNotExist: - return JsonResponse({"message": "No CPDAReimbursement forms found."}, status=404) - - - diff --git a/FusionIIIT/applications/programme_curriculum/migrations/0026_add_database_indexes.py b/FusionIIIT/applications/programme_curriculum/migrations/0026_add_database_indexes.py index 2afda5843..e46ca6c4f 100644 --- a/FusionIIIT/applications/programme_curriculum/migrations/0026_add_database_indexes.py +++ b/FusionIIIT/applications/programme_curriculum/migrations/0026_add_database_indexes.py @@ -17,24 +17,56 @@ class Migration(migrations.Migration): sql=[ # Main composite index for course registration queries """ - CREATE INDEX IF NOT EXISTS idx_course_reg_main_query - ON course_registration(session, semester_type, course_id_id, registration_type, student_id_id); + DO $$ + BEGIN + IF EXISTS ( + SELECT 1 FROM information_schema.tables + WHERE table_name = 'course_registration' + ) THEN + CREATE INDEX IF NOT EXISTS idx_course_reg_main_query + ON course_registration(session, semester_type, course_id_id, registration_type, student_id_id); + END IF; + END $$; """, # Individual indexes for course registration """ - CREATE INDEX IF NOT EXISTS idx_course_reg_session_semester_course - ON course_registration(session, semester_type, course_id_id); + DO $$ + BEGIN + IF EXISTS ( + SELECT 1 FROM information_schema.tables + WHERE table_name = 'course_registration' + ) THEN + CREATE INDEX IF NOT EXISTS idx_course_reg_session_semester_course + ON course_registration(session, semester_type, course_id_id); + END IF; + END $$; """, """ - CREATE INDEX IF NOT EXISTS idx_course_reg_student - ON course_registration(student_id_id); + DO $$ + BEGIN + IF EXISTS ( + SELECT 1 FROM information_schema.tables + WHERE table_name = 'course_registration' + ) THEN + CREATE INDEX IF NOT EXISTS idx_course_reg_student + ON course_registration(student_id_id); + END IF; + END $$; """, """ - CREATE INDEX IF NOT EXISTS idx_course_reg_type - ON course_registration(registration_type); + DO $$ + BEGIN + IF EXISTS ( + SELECT 1 FROM information_schema.tables + WHERE table_name = 'course_registration' + ) THEN + CREATE INDEX IF NOT EXISTS idx_course_reg_type + ON course_registration(registration_type); + END IF; + END $$; """ ], diff --git a/scripts/hr2_rbac_evaluator.ps1 b/scripts/hr2_rbac_evaluator.ps1 new file mode 100644 index 000000000..225ebc425 --- /dev/null +++ b/scripts/hr2_rbac_evaluator.ps1 @@ -0,0 +1,269 @@ +param( + [string]$BaseUrl = "http://127.0.0.1:8000", + [string]$ApiPrefix = "" # set to "/api/hr" only if your project mounts these urls under that prefix +) + +$ErrorActionPreference = "Stop" + +# Update these if your evaluator uses different credentials +$RoleUsers = @{ + "employee" = @{ username = "rahul1001"; password = "rahul123" } + "hod" = @{ username = "hod1002"; password = "hod123" } + "director" = @{ username = "director1003"; password = "director123" } + "registrar" = @{ username = "registrar1004"; password = "registrar123" } + "hradmin" = @{ username = "hradmin1005"; password = "hradmin123" } + "accountant" = @{ username = "accountant1006"; password = "accountant123" } +} + +function Join-ApiUrl { + param( + [string]$Base, + [string]$Prefix, + [string]$Path + ) + + $base = $Base.TrimEnd('/') + $prefix = $Prefix.Trim('/') + $path = $Path.TrimStart('/') + + if ([string]::IsNullOrWhiteSpace($prefix)) { + return "$base/$path" + } + + return "$base/$prefix/$path" +} + +function Resolve-Token { + param([object]$Response) + + if ($null -eq $Response) { return $null } + if ($Response.token) { return $Response.token } + if ($Response.key) { return $Response.key } + if ($Response.auth_token) { return $Response.auth_token } + if ($Response.data -and $Response.data.token) { return $Response.data.token } + return $null +} + +function Login-Role { + param([string]$Role) + + if (-not $RoleUsers.ContainsKey($Role)) { + throw "Unknown role '$Role'. Available roles: $($RoleUsers.Keys -join ', ')" + } + + $creds = $RoleUsers[$Role] + $uri = Join-ApiUrl -Base $BaseUrl -Prefix $ApiPrefix -Path "/api/auth/login/" + $body = @{ username = $creds.username; password = $creds.password } | ConvertTo-Json + + try { + $resp = Invoke-RestMethod -Method Post -Uri $uri -ContentType "application/json" -Body $body + $token = Resolve-Token -Response $resp + + if (-not $token) { + throw "Login succeeded but no token field was found in the response." + } + + return [pscustomobject]@{ + role = $Role + username = $creds.username + token = $token + } + } + catch { + throw "Login failed for role '$Role' (user: $($creds.username)): $($_.Exception.Message)" + } +} + +function Invoke-ApiAsRole { + param( + [Parameter(Mandatory = $true)][string]$Role, + [Parameter(Mandatory = $true)][string]$Path, + [ValidateSet("GET", "POST", "PUT", "PATCH", "DELETE")][string]$Method = "GET", + [hashtable]$Body = $null + ) + + $session = Login-Role -Role $Role + $uri = Join-ApiUrl -Base $BaseUrl -Prefix $ApiPrefix -Path $Path + $headers = @{ + Authorization = "Token $($session.token)" + Accept = "application/json" + } + + try { + $params = @{ + Method = $Method + Uri = $uri + Headers = $headers + ErrorAction = "Stop" + MaximumRedirection = 0 # important: lets you see 301 instead of silently following it + } + + if ($Body) { + $params.ContentType = "application/json" + $params.Body = ($Body | ConvertTo-Json -Depth 10) + } + + $resp = Invoke-WebRequest @params + + $content = $resp.Content + $parsed = $content + try { + if ($content) { + $parsed = $content | ConvertFrom-Json + } + } catch { + $parsed = $content + } + + return [pscustomobject]@{ + role = $Role + method = $Method + path = $Path + uri = $uri + status = [int]$resp.StatusCode + ok = $true + response = $parsed + } + } + catch { + $status = 0 + $raw = "" + + if ($_.Exception.Response) { + try { + $status = [int]$_.Exception.Response.StatusCode + $stream = $_.Exception.Response.GetResponseStream() + if ($stream) { + $reader = New-Object System.IO.StreamReader($stream) + $raw = $reader.ReadToEnd() + $reader.Close() + } + } catch { + $raw = $_.Exception.Message + } + } else { + $raw = $_.Exception.Message + } + + return [pscustomobject]@{ + role = $Role + method = $Method + path = $Path + uri = $uri + status = $status + ok = $false + response = $raw + } + } +} + +function Test-RbacCase { + param( + [Parameter(Mandatory = $true)][string]$ActorRole, + [Parameter(Mandatory = $true)][string]$Path, + [ValidateSet("GET", "POST", "PUT", "PATCH", "DELETE")][string]$Method = "GET", + [int[]]$ExpectedUnauthorizedCodes = @(401, 403, 301), + [hashtable]$Body = $null + ) + + $result = Invoke-ApiAsRole -Role $ActorRole -Path $Path -Method $Method -Body $Body + $pass = $ExpectedUnauthorizedCodes -contains $result.status + + [pscustomobject]@{ + actor = $ActorRole + method = $Method + path = $Path + observedStatus = $result.status + expectedUnauthorized = ($ExpectedUnauthorizedCodes -join ",") + pass = $pass + response = $result.response + } +} + +# Representative endpoints from your hr_api urls +$RbacCases = @( + @{ Name = "employees-list"; Path = "/employees/"; Method = "GET" }, + @{ Name = "employees-detail"; Path = "/employees/1/"; Method = "GET" }, + + @{ Name = "leave-list-create"; Path = "/leave-applications/"; Method = "GET" }, + @{ Name = "leave-detail"; Path = "/leave-applications/1/"; Method = "GET" }, + @{ Name = "leave-balance"; Path = "/leave-balance/"; Method = "GET" }, + @{ Name = "leave-balance-other"; Path = "/leave-balance/1/"; Method = "GET" }, + @{ Name = "leave-responsibility"; Path = "/leave-applications/1/responsibility/reviewer/"; Method = "POST" }, + @{ Name = "leave-request-document"; Path = "/leave-applications/1/request-document/"; Method = "POST" }, + @{ Name = "leave-submit-document"; Path = "/leave-applications/1/submit-document/"; Method = "POST" }, + @{ Name = "leave-download"; Path = "/leave-applications/1/download/"; Method = "GET" }, + @{ Name = "leave-withdraw"; Path = "/leave-applications/1/withdraw/"; Method = "POST" }, + @{ Name = "leave-cancel-request"; Path = "/leave-applications/1/cancel-request/"; Method = "POST" }, + @{ Name = "leave-cancel-decision"; Path = "/leave-applications/1/cancel-decision/approve/"; Method = "POST" }, + @{ Name = "leave-extension-request"; Path = "/leave-applications/1/extension-request/"; Method = "POST" }, + @{ Name = "leave-extension-decision"; Path = "/leave-applications/1/extension-decision/approve/"; Method = "POST" }, + @{ Name = "leave-resumption"; Path = "/leave-applications/1/resumption/"; Method = "POST" }, + @{ Name = "leave-resumption-decision"; Path = "/leave-applications/1/resumption-decision/approve/"; Method = "POST" }, + @{ Name = "leave-decision"; Path = "/leave-applications/1/approve/"; Method = "POST" }, + @{ Name = "leave-nominee-dashboard"; Path = "/leave-nominee/"; Method = "GET" }, + @{ Name = "leave-nominee-decision"; Path = "/leave-nominee/1/"; Method = "POST" }, + + @{ Name = "attendance"; Path = "/attendance/"; Method = "GET" }, + + @{ Name = "appraisal-periods"; Path = "/appraisal-periods/"; Method = "GET" }, + @{ Name = "appraisals"; Path = "/appraisals/"; Method = "GET" }, + + @{ Name = "training-programs"; Path = "/training-programs/"; Method = "GET" }, + @{ Name = "training-nominations"; Path = "/training-nominations/"; Method = "POST" }, + + @{ Name = "promotions"; Path = "/promotions/"; Method = "POST" }, + + @{ Name = "workload"; Path = "/workload/"; Method = "GET" }, + + @{ Name = "ltc-list-create"; Path = "/ltc/"; Method = "GET" }, + @{ Name = "ltc-detail"; Path = "/ltc/1/"; Method = "GET" }, + @{ Name = "ltc-download"; Path = "/ltc/1/download/"; Method = "GET" }, + @{ Name = "ltc-withdraw"; Path = "/ltc/1/withdraw/"; Method = "POST" }, + @{ Name = "ltc-decision"; Path = "/ltc/1/approve/"; Method = "POST" }, + + @{ Name = "cpda-advance-list"; Path = "/cpda-advances/"; Method = "GET" }, + @{ Name = "cpda-advance-detail"; Path = "/cpda-advances/1/"; Method = "GET" }, + @{ Name = "cpda-advance-download"; Path = "/cpda-advances/1/download/"; Method = "GET" }, + @{ Name = "cpda-advance-withdraw"; Path = "/cpda-advances/1/withdraw/"; Method = "POST" }, + @{ Name = "cpda-advance-decision"; Path = "/cpda-advances/1/approve/"; Method = "POST" }, + + @{ Name = "cpda-reimbursement-list"; Path = "/cpda-reimbursements/"; Method = "GET" }, + @{ Name = "cpda-reimbursement-detail"; Path = "/cpda-reimbursements/1/"; Method = "GET" }, + @{ Name = "cpda-reimbursement-decision"; Path = "/cpda-reimbursements/1/approve/"; Method = "POST" }, + + @{ Name = "appraisal-form-list"; Path = "/appraisal-forms/"; Method = "GET" }, + @{ Name = "appraisal-form-detail"; Path = "/appraisal-forms/1/"; Method = "GET" }, + @{ Name = "appraisal-form-download"; Path = "/appraisal-forms/1/download/"; Method = "GET" }, + @{ Name = "appraisal-form-review"; Path = "/appraisal-forms/1/review/"; Method = "POST" }, + @{ Name = "appraisal-form-assign"; Path = "/appraisal-forms/1/assign/"; Method = "POST" } +) + +function Invoke-RbacSweep { + param( + [string[]]$Roles = @("employee", "hod", "director", "registrar", "hradmin", "accountant") + ) + + $results = foreach ($case in $RbacCases) { + foreach ($role in $Roles) { + Test-RbacCase -ActorRole $role -Path $case.Path -Method $case.Method + } + } + + $results | Select-Object actor, method, path, observedStatus, expectedUnauthorized, pass | + Format-Table -AutoSize +} + +function Show-RbacMatrix { + param([string]$Path) + + $roles = @("employee", "hod", "director", "registrar", "hradmin", "accountant") + $rows = foreach ($role in $roles) { + Invoke-ApiAsRole -Role $role -Path $Path -Method "GET" + } + + $rows | Select-Object role, method, path, status, ok | Format-Table -AutoSize +} + +Write-Host "Loaded RBAC evaluator for $BaseUrl" -ForegroundColor Green +Write-Host "Functions: Login-Role, Invoke-ApiAsRole, Test-RbacCase, Show-RbacMatrix, Invoke-RbacSweep" -ForegroundColor Cyan \ No newline at end of file