import json from datetime import datetime, timedelta from urllib.parse import parse_qs from django.contrib import messages from django.contrib.auth.models import User from django.http import HttpResponse, JsonResponse from django.shortcuts import redirect, render from django.utils.translation import gettext_lazy as _ from attendance.methods.group_by import group_by_queryset as group_by from base.context_processors import intial_notice_period from base.methods import closest_numbers, sortby from base.views import paginator_qry from employee.models import Employee from horilla.decorators import login_required, manager_can_enter, permission_required from notifications.signals import notify from offboarding.decorators import ( any_manager_can_enter, check_feature_enabled, offboarding_manager_can_enter, offboarding_or_stage_manager_can_enter, ) from offboarding.filters import ( LetterFilter, LetterReGroup, PipelineEmployeeFilter, PipelineFilter, PipelineStageFilter, ) from offboarding.forms import ( NoteForm, OffboardingEmployeeForm, OffboardingForm, OffboardingStageForm, ResignationLetterForm, StageSelectForm, TaskForm, ) from offboarding.models import ( EmployeeTask, Offboarding, OffboardingEmployee, OffboardingGeneralSetting, OffboardingNote, OffboardingStage, OffboardingStageMultipleFile, OffboardingTask, ResignationLetter, ) from onboarding.filters import OnboardingStageFilter from payroll.models.models import Contract, PayrollGeneralSetting from recruitment.pipeline_grouper import group_by_queryset def pipeline_grouper(filters={}, offboardings=[]): groups = [] for offboarding in offboardings: employees = [] stages = PipelineStageFilter( filters, queryset=offboarding.offboardingstage_set.all() ).qs.order_by("id") all_stages_grouper = [] data = {"offboarding": offboarding, "stages": [], "employees": []} for stage in stages: all_stages_grouper.append({"grouper": stage, "list": []}) stage_employees = PipelineEmployeeFilter( filters, OffboardingEmployee.objects.filter( stage_id=stage, employee_id__is_active=True ), ).qs.order_by("stage_id__id") page_name = "page" + stage.title + str(offboarding.id) employee_grouper = group_by_queryset( stage_employees, "stage_id", filters.get(page_name), page_name, ).object_list employees = employees + [ employee.id for employee in stage.offboardingemployee_set.all() ] data["stages"] = data["stages"] + employee_grouper ordered_data = [] # combining un used groups in to the grouper groupers = data["stages"] for stage in stages: found = False for grouper in groupers: if grouper["grouper"] == stage: ordered_data.append(grouper) found = True break if not found: ordered_data.append({"grouper": stage}) data = { "offboarding": offboarding, "stages": ordered_data, "employee_ids": employees, } groups.append(data) return groups @login_required @any_manager_can_enter( "offboarding.view_offboarding", offboarding_employee_can_enter=True ) def pipeline(request): """ Offboarding pipeline view """ offboardings = PipelineFilter().qs groups = pipeline_grouper({}, offboardings) for item in groups: setattr(item["offboarding"], "stages", item["stages"]) stage_forms = {} for offboarding in offboardings: stage_forms[str(offboarding.id)] = StageSelectForm(offboarding=offboarding) filter_dict = parse_qs(request.GET.urlencode()) return render( request, "offboarding/pipeline/pipeline.html", { "offboardings": groups, "employee_filter": PipelineEmployeeFilter(), "pipeline_filter": PipelineFilter(), "stage_filter": PipelineStageFilter(), "stage_forms": stage_forms, "filter_dict": filter_dict, "today": datetime.today().date(), }, ) @login_required @permission_required("offboarding_view_offboardingemployee") def filter_pipeline(request): """ This method is used filter offboarding process """ offboardings = PipelineFilter(request.GET).qs groups = pipeline_grouper(request.GET, offboardings) for item in groups: setattr(item["offboarding"], "stages", item["stages"]) stage_forms = {} for offboarding in offboardings: stage_forms[str(offboarding.id)] = StageSelectForm(offboarding=offboarding) return render( request, "offboarding/pipeline/offboardings.html", { "offboardings": groups, "stage_forms": stage_forms, "filter_dict": parse_qs(request.GET.urlencode()), }, ) @login_required @permission_required("offboarding.add_offboarding") def create_offboarding(request): """ Create offboarding view """ instance_id = eval(str(request.GET.get("instance_id"))) instance = None if instance_id and isinstance(instance_id, int): instance = Offboarding.objects.filter(id=instance_id).first() form = OffboardingForm(instance=instance) if request.method == "POST": form = OffboardingForm(request.POST, instance=instance) if form.is_valid(): form.save() messages.success(request, _("Offboarding saved")) return HttpResponse("") return render( request, "offboarding/pipeline/form.html", { "form": form, }, ) @login_required @permission_required("offboarding.delete_offboarding") def delete_offboarding(request): """ This method is used to delete offboardings """ ids = request.GET.getlist("id") Offboarding.objects.filter(id__in=ids).delete() messages.success(request, _("Offboarding deleted")) return redirect(pipeline) @login_required @offboarding_manager_can_enter("offboarding.add_offboardingstage") def create_stage(request): """ This method is used to create stages for offboardings """ offboarding_id = request.GET["offboarding_id"] instance_id = eval(str(request.GET.get("instance_id"))) instance = None if instance_id and isinstance(instance_id, int): instance = OffboardingStage.objects.get(id=instance_id) offboarding = Offboarding.objects.get(id=offboarding_id) form = OffboardingStageForm(instance=instance) form.instance.offboarding_id = offboarding if request.method == "POST": form = OffboardingStageForm(request.POST, instance=instance) if form.is_valid(): instance = form.save(commit=False) instance.offboarding_id = offboarding instance.save() instance.managers.set(form.data.getlist("managers")) messages.success(request, _("Stage saved")) return HttpResponse("") return render(request, "offboarding/stage/form.html", {"form": form}) @login_required @any_manager_can_enter("offboarding.add_offboardingemployee") def add_employee(request): """ This method is used to add employee to the stage """ default_notice_period = ( intial_notice_period(request)["get_initial_notice_period"] if intial_notice_period(request)["get_initial_notice_period"] else 0 ) end_date = datetime.today() + timedelta(days=default_notice_period) stage_id = request.GET["stage_id"] instance_id = eval(str(request.GET.get("instance_id"))) instance = None if instance_id and isinstance(instance_id, int): instance = OffboardingEmployee.objects.get(id=instance_id) stage = OffboardingStage.objects.get(id=stage_id) form = OffboardingEmployeeForm( initial={"stage_id": stage, "notice_period_ends": end_date}, instance=instance ) form.instance.stage_id = stage if request.method == "POST": form = OffboardingEmployeeForm(request.POST, instance=instance) if form.is_valid(): instance = form.save(commit=False) instance.stage_id = stage instance.save() messages.success(request, _("Employee saved")) if not instance_id: notify.send( request.user.employee_get, recipient=instance.employee_id.employee_user_id, verb=f"You have been added to the {stage} of {stage.offboarding_id}", verb_ar=f"", verb_de=f"", verb_es=f"", verb_fr=f"", redirect="offboarding/offboarding-pipeline", icon="information", ) return HttpResponse("") return render(request, "offboarding/employee/form.html", {"form": form}) @login_required @permission_required("offboarding.delete_offboardingemployee") def delete_employee(request): """ This method is used to delete the offboarding employee """ employee_ids = request.GET.getlist("employee_ids") instances = OffboardingEmployee.objects.filter(id__in=employee_ids) if instances: instances.delete() messages.success(request, _("Offboarding employee deleted")) notify.send( request.user.employee_get, recipient=User.objects.filter( id__in=instances.values_list("employee_id__employee_user_id", flat=True) ), verb=f"You have been removed from the offboarding", verb_ar=f"", verb_de=f"", verb_es=f"", verb_fr=f"", redirect="offboarding/offboarding-pipeline", icon="information", ) else: messages.error(request, _("Employees note found")) return redirect(pipeline) @login_required @permission_required("offboarding.delete_offboardingstage") def delete_stage(request): """ This method is used to delete the offboarding stage """ ids = request.GET.getlist("ids") OffboardingStage.objects.filter(id__in=ids).delete() messages.success(request, _("Stage deleted")) return redirect(pipeline) @login_required @any_manager_can_enter("offboarding.change_offboarding") def change_stage(request): """ This method is used to update the stages of the employee """ employee_ids = request.GET.getlist("employee_ids") stage_id = request.GET["stage_id"] employees = OffboardingEmployee.objects.filter(id__in=employee_ids) stage = OffboardingStage.objects.get(id=stage_id) # This wont trigger the save method inside the offboarding employee # employees.update(stage_id=stage) for employee in employees: employee.stage_id = stage employee.save() # if stage.type == "archived": # Employee.objects.filter( # id__in=employees.values_list("employee_id__id", flat=True) # ).update(is_active=False) stage_forms = {} stage_forms[str(stage.offboarding_id.id)] = StageSelectForm( offboarding=stage.offboarding_id ) notify.send( request.user.employee_get, recipient=User.objects.filter( id__in=employees.values_list("employee_id__employee_user_id", flat=True) ), verb=f"Offboarding stage has been changed", verb_ar=f"", verb_de=f"", verb_es=f"", verb_fr=f"", redirect="offboarding/offboarding-pipeline", icon="information", ) groups = pipeline_grouper({}, [stage.offboarding_id]) for item in groups: setattr(item["offboarding"], "stages", item["stages"]) return render( request, "offboarding/stage/offboarding_body.html", { "offboarding": groups[0], "stage_forms": stage_forms, "response_message": _("stage changed successfully."), "today": datetime.today().date(), }, ) @login_required @any_manager_can_enter( "offboarding.view_offboardingnote", offboarding_employee_can_enter=True ) def view_notes(request, employee_id=None): """ This method is used to render all the notes of the employee """ if request.FILES: files = request.FILES.getlist("files") note_id = request.GET["note_id"] note = OffboardingNote.objects.get(id=note_id) attachments = [] for file in files: attachment = OffboardingStageMultipleFile() attachment.attachment = file attachment.save() attachments.append(attachment) note.attachments.add(*attachments) offboarding_employee_id = employee_id employee = OffboardingEmployee.objects.get(id=offboarding_employee_id) return render( request, "offboarding/note/view_notes.html", { "employee": employee, }, ) @login_required # @any_manager_can_enter("offboarding.add_offboardingnote") def add_note(request): """ This method is used to create note for the offboarding employee """ employee_id = request.GET["employee_id"] employee = OffboardingEmployee.objects.get(id=employee_id) form = NoteForm() if request.method == "POST": form = NoteForm(request.POST, request.FILES) form.instance.employee_id = employee if form.is_valid(): form.save() messages.success(request, _("Note added successfully")) return redirect("view-offboarding-note", employee_id=employee.id) return render( request, "offboarding/note/view_notes.html", { "form": form, "employee": employee, }, ) @login_required @manager_can_enter(perm="offboarding.delete_offboardingNote") def offboarding_note_delete(request, note_id): """ This method is used to delete the offboarding note """ try: note = OffboardingNote.objects.get(id=note_id) employee = note.employee_id.id note.delete() messages.success(request, _("Note deleted")) except OffboardingNote.DoesNotExist: messages.error(request, _("Note not found.")) return redirect("view-offboarding-note", employee_id=employee) @login_required @permission_required("offboarding.delete_offboardingnote") def delete_attachment(request): """ Used to delete attachment """ ids = request.GET.getlist("ids") OffboardingStageMultipleFile.objects.filter(id__in=ids).delete() offboarding_employee_id = request.GET["employee_id"] employee = OffboardingEmployee.objects.get(id=offboarding_employee_id) return render( request, "offboarding/note/view_notes.html", { "employee": employee, }, ) @login_required @offboarding_or_stage_manager_can_enter("offboarding.add_offboardingtask") def add_task(request): """ This method is used to add offboarding tasks """ stage_id = request.GET.get("stage_id") instance_id = eval(str(request.GET.get("instance_id"))) employees = OffboardingEmployee.objects.filter(stage_id=stage_id) instance = None if instance_id: instance = OffboardingTask.objects.filter(id=instance_id).first() form = TaskForm( initial={ "stage_id": stage_id, "tasks_to": employees, }, instance=instance, ) if request.method == "POST": form = TaskForm( request.POST, instance=instance, initial={ "stage_id": stage_id, }, ) if form.is_valid(): form.save() messages.success(request, _("Task Added")) return HttpResponse("") return render( request, "offboarding/task/form.html", { "form": form, }, ) @login_required @any_manager_can_enter( "offboarding.change_employeetask", offboarding_employee_can_enter=True ) def update_task_status(request, *args, **kwargs): """ This method is used to update the assigned tasks status """ stage_id = request.GET["stage_id"] employee_ids = request.GET.getlist("employee_ids") task_id = request.GET["task_id"] status = request.GET["task_status"] employee_task = EmployeeTask.objects.filter( employee_id__id__in=employee_ids, task_id__id=task_id ) employee_task.update(status=status) notify.send( request.user.employee_get, recipient=User.objects.filter( id__in=employee_task.values_list( "task_id__managers__employee_user_id", flat=True ) ), verb=f"Offboarding Task status has been updated", verb_ar=f"", verb_de=f"", verb_es=f"", verb_fr=f"", redirect="offboarding/offboarding-pipeline", icon="information", ) stage = OffboardingStage.objects.get(id=stage_id) stage_forms = {} stage_forms[str(stage.offboarding_id.id)] = StageSelectForm( offboarding=stage.offboarding_id ) groups = pipeline_grouper({}, [stage.offboarding_id]) for item in groups: setattr(item["offboarding"], "stages", item["stages"]) return render( request, "offboarding/stage/offboarding_body.html", { "offboarding": groups[0], "stage_forms": stage_forms, "response_message": _("Task status changed successfully."), }, ) @login_required @any_manager_can_enter("offboarding.add_employeetask") def task_assign(request): """ This method is used to assign task to employees """ employee_ids = request.GET.getlist("employee_ids") task_id = request.GET["task_id"] employees = OffboardingEmployee.objects.filter(id__in=employee_ids) task = OffboardingTask.objects.get(id=task_id) for employee in employees: try: assigned_task = EmployeeTask() assigned_task.employee_id = employee assigned_task.task_id = task assigned_task.save() except: pass offboarding = employees.first().stage_id.offboarding_id stage_forms = {} stage_forms[str(offboarding.id)] = StageSelectForm(offboarding=offboarding) groups = pipeline_grouper({}, [task.stage_id.offboarding_id]) for item in groups: setattr(item["offboarding"], "stages", item["stages"]) return render( request, "offboarding/stage/offboarding_body.html", { "offboarding": groups[0], "stage_forms": stage_forms, "response_message": _("Task Assigned"), "today": datetime.today().date(), }, ) @login_required @permission_required("offboarding.delete_offboardingtask") def delete_task(request): """ This method is used to delete the task """ task_ids = request.GET.getlist("task_ids") OffboardingTask.objects.filter(id__in=task_ids).delete() messages.success(request, _("Task deleted")) return redirect(pipeline) @login_required def offboarding_individual_view(request, emp_id): """ This method is used to get the individual view of the offboarding employees parameters: emp_id(int): the id of the offboarding employee """ employee = OffboardingEmployee.objects.get(id=emp_id) tasks = EmployeeTask.objects.filter(employee_id=emp_id) stage_forms = {} offboarding_stages = OffboardingStage.objects.filter( offboarding_id=employee.stage_id.offboarding_id ) stage_forms[str(employee.stage_id.offboarding_id.id)] = StageSelectForm( offboarding=employee.stage_id.offboarding_id ) context = { "employee": employee, "tasks": tasks, "choices": EmployeeTask.statuses, "offboarding_stages": offboarding_stages, "stage_forms": stage_forms, } requests_ids_json = request.GET.get("requests_ids") if requests_ids_json: requests_ids = json.loads(requests_ids_json) previous_id, next_id = closest_numbers(requests_ids, emp_id) context["requests_ids"] = requests_ids_json context["previous"] = previous_id context["next"] = next_id return render(request, "offboarding/pipeline/individual_view.html", context) @login_required @permission_required("offboarding.view_resignationletter") @check_feature_enabled("resignation_request") def request_view(request): """ This method is used to view the resignation request """ defatul_filter = {"status": "requested"} filter_instance = LetterFilter(defatul_filter) offboardings = Offboarding.objects.all() return render( request, "offboarding/resignation/requests_view.html", { "letters": paginator_qry(filter_instance.qs, request.GET.get("page")), "f": filter_instance, "filter_dict": {"status": ["Requested"]}, "offboardings": offboardings, "gp_fields": LetterReGroup.fields, }, ) @login_required @permission_required("offboarding.view_resignationletter") def request_single_view(request, id): letter = ResignationLetter.objects.get(id=id) context = { "letter": letter, } requests_ids_json = request.GET.get("requests_ids") if requests_ids_json: requests_ids = json.loads(requests_ids_json) previous_id, next_id = closest_numbers(requests_ids, id) context["requests_ids"] = requests_ids_json context["previous"] = previous_id context["next"] = next_id return render( request, "offboarding/resignation/request_single_view.html", context, ) @login_required @check_feature_enabled("resignation_request") def search_resignation_request(request): """ This method is used to search/filter the letter """ if request.user.has_perm("offboarding.view_resignationletter"): letters = LetterFilter(request.GET).qs else: letters = ResignationLetter.objects.filter( employee_id__employee_user_id=request.user ) field = request.GET.get("field") data_dict = parse_qs(request.GET.urlencode()) template = "offboarding/resignation/request_cards.html" if request.GET.get("view") == "list": template = "offboarding/resignation/request_list.html" if request.GET.get("sortby"): letters = sortby(request, letters, "sortby") data_dict.pop("sortby") if field != "" and field is not None: letters = group_by(letters, field, request.GET.get("page"), "page") list_values = [entry["list"] for entry in letters] id_list = [] for value in list_values: for instance in value.object_list: id_list.append(instance.id) requests_ids = json.dumps(list(id_list)) template = "offboarding/resignation/group_by.html" else: letters = paginator_qry(letters, request.GET.get("page")) requests_ids = json.dumps([instance.id for instance in letters.object_list]) if request.GET.get("view"): data_dict.pop("view") pagination = ( False if request.META.get("HTTP_REFERER") and request.META.get("HTTP_REFERER").endswith("employee-profile/") else True ) return render( request, template, { "letters": letters, "filter_dict": data_dict, "pd": request.GET.urlencode(), "pagination": pagination, "requests_ids": requests_ids, "field": field, }, ) @login_required @check_feature_enabled("resignation_request") def delete_resignation_request(request): """ This method is used to delete resignation letter instance """ ids = request.GET.getlist("letter_ids") ResignationLetter.objects.filter(id__in=ids).delete() messages.success(request, _("Resignation letter deleted")) if request.META.get("HTTP_REFERER") and request.META.get("HTTP_REFERER").endswith( "employee-profile/" ): return redirect("/employee/employee-profile/") else: return redirect(request_view) @login_required @check_feature_enabled("resignation_request") def create_resignation_request(request): """ This method is used to render form to create resignation requests """ instance_id = eval(str(request.GET.get("instance_id"))) instance = None if instance_id: instance = ResignationLetter.objects.get(id=instance_id) form = ResignationLetterForm(instance=instance) if request.method == "POST": form = ResignationLetterForm(request.POST, instance=instance) if form.is_valid(): form.save() messages.success(request, _("Resignation letter saved")) return HttpResponse("") return render(request, "offboarding/resignation/form.html", {"form": form}) @login_required @check_feature_enabled("resignation_request") @permission_required("offboarding.change_resignationletter") def update_status(request): """ This method is used to update the status of resignation letter """ ids = request.GET.getlist("letter_ids") status = request.GET["status"] offboarding_id = request.GET.get("offboarding_id") default_notice_end = PayrollGeneralSetting.objects.first() if offboarding_id: offboarding = Offboarding.objects.get(id=offboarding_id) notice_period_starts = request.GET.get("notice_period_starts") notice_period_ends = request.GET.get("notice_period_ends") if notice_period_starts: notice_period_starts = datetime.strptime( notice_period_starts, "%Y-%m-%d" ).date() today = datetime.today() if notice_period_ends: notice_period_ends = datetime.strptime( notice_period_ends, "%Y-%m-%d" ).date() else: notice_period_ends = None if default_notice_end: notice_period_ends = notice_period_starts + timedelta( days=default_notice_end.notice_period ) if not notice_period_starts: notice_period_starts = today letters = ResignationLetter.objects.filter(id__in=ids) # if use update method instead of save then save method will not trigger if status in ["approved", "rejected"]: for letter in letters: letter.status = status letter.save() if status == "approved": letter.to_offboarding_employee( offboarding, notice_period_starts, notice_period_ends ) messages.success( request, f"Resignation request has been {letter.get_status_display()}" ) notify.send( request.user.employee_get, recipient=letter.employee_id.employee_user_id, verb=f"Resignation request has been {letter.get_status_display()}", verb_ar=f"", verb_de=f"", verb_es=f"", verb_fr=f"", redirect="#", icon="information", ) return redirect(request_view) @login_required @permission_required("offboarding.add_offboardinggeneralsetting") def enable_resignation_request(request): """ Enable disable resignation letter feature """ resignation_request_feature = OffboardingGeneralSetting.objects.first() resignation_request_feature = ( resignation_request_feature if resignation_request_feature else OffboardingGeneralSetting() ) resignation_request_feature.resignation_request = ( "resignation_request" in request.GET.keys() ) resignation_request_feature.save() return HttpResponse("Success") @login_required @permission_required("offboarding.add_offboardingemployee") def get_notice_period(request): """ This method is used to get initial details for notice period """ employee_id = request.GET["employee_id"] employee_contract = ( (Contract.objects.order_by("-id").filter(employee_id__id=employee_id).first()) if Contract.objects.filter( employee_id__id=employee_id, contract_status="active" ).first() else Contract.objects.filter( employee_id__id=employee_id, contract_status="active" ).first() ) response = { "notice_period": intial_notice_period(request)["get_initial_notice_period"], "unit": "month", "notice_period_starts": str(datetime.today().date()), } if employee_contract: response["notice_period"] = employee_contract.notice_period_in_days return JsonResponse(response) def get_notice_period_end_date(request): start_date = request.GET.get("start_date") start_date = datetime.strptime(start_date, "%Y-%m-%d").date() notice_period = intial_notice_period(request)["get_initial_notice_period"] end_date = start_date + timedelta(days=notice_period) response = { "end_date": end_date, } return JsonResponse(response)