diff --git a/employee/admin.py b/employee/admin.py index 94fca0192..4472e0185 100644 --- a/employee/admin.py +++ b/employee/admin.py @@ -4,7 +4,15 @@ admin.py This page is used to register the model with admins site. """ from django.contrib import admin -from employee.models import Employee, EmployeeWorkInformation, EmployeeBankDetails, EmployeeNote, EmployeeTag +from employee.models import ( + Employee, + EmployeeWorkInformation, + EmployeeBankDetails, + EmployeeNote, + EmployeeTag, + PolicyMultipleFile, + Policy, +) from simple_history.admin import SimpleHistoryAdmin @@ -13,4 +21,4 @@ from simple_history.admin import SimpleHistoryAdmin admin.site.register(Employee) admin.site.register(EmployeeBankDetails) admin.site.register(EmployeeWorkInformation, SimpleHistoryAdmin) -admin.site.register([EmployeeNote, EmployeeTag]) +admin.site.register([EmployeeNote, EmployeeTag, PolicyMultipleFile, Policy]) diff --git a/employee/filters.py b/employee/filters.py index 2a87a5a32..9dd6f787a 100644 --- a/employee/filters.py +++ b/employee/filters.py @@ -4,6 +4,7 @@ filters.py This page is used to register filter for employee models """ +from employee.models import Policy import uuid from django import forms import django_filters @@ -84,7 +85,7 @@ class EmployeeFilter(FilterSet): "employee_work_info__reporting_manager_id", "employee_work_info__company_id", "employee_work_info__shift_id", - "employee_work_info__tags" + "employee_work_info__tags", ] def not_in_yet_func(self, queryset, _, value): @@ -196,3 +197,15 @@ class EmployeeReGroup: ("employee_work_info.reporting_manager_id", "Reporting Manager"), ("employee_work_info.company_id", "Company"), ] + + +class PolicyFilter(FilterSet): + """ + PolicyFilter filterset class + """ + + search = django_filters.CharFilter(field_name="title", lookup_expr="icontains") + + class Meta: + model = Policy + fields = "__all__" diff --git a/employee/forms.py b/employee/forms.py index 37cd73ae6..77ba87692 100644 --- a/employee/forms.py +++ b/employee/forms.py @@ -27,7 +27,14 @@ from django.contrib.auth.models import User from django.forms import DateInput, TextInput from django.utils.translation import gettext as _ from django.utils.translation import gettext_lazy as trans -from employee.models import Employee, EmployeeWorkInformation, EmployeeBankDetails, EmployeeNote +from employee.models import ( + Employee, + EmployeeWorkInformation, + EmployeeBankDetails, + EmployeeNote, + Policy, + PolicyMultipleFile, +) from base.methods import reload_queryset @@ -405,12 +412,69 @@ class EmployeeNoteForm(ModelForm): """ model = EmployeeNote - exclude = ( - "updated_by", - ) + exclude = ("updated_by",) fields = "__all__" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) field = self.fields["employee_id"] - field.widget = field.hidden_widget() \ No newline at end of file + field.widget = field.hidden_widget() + + +class MultipleFileInput(forms.ClearableFileInput): + allow_multiple_selected = True + + +class MultipleFileField(forms.FileField): + def __init__(self, *args, **kwargs): + kwargs.setdefault("widget", MultipleFileInput()) + super().__init__(*args, **kwargs) + + def clean(self, data, initial=None): + single_file_clean = super().clean + if isinstance(data, (list, tuple)): + result = [single_file_clean(d, initial) for d in data] + else: + result = [single_file_clean(data, initial)] + if len(result) == 0: + result = [[]] + return result[0] + + +class PolicyForm(ModelForm): + """ + PolicyForm + """ + + class Meta: + model = Policy + fields = "__all__" + exclude = ["attachments"] + widgets = { + "body": forms.Textarea( + attrs={"data-summernote": "", "style": "display:none;"} + ), + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["attachment"] = MultipleFileField( + label="Attachements", required=False + ) + + def save(self, *args, commit=True, **kwargs): + attachemnt = [] + multiple_attachment_ids = [] + attachemnts = None + if self.files.getlist("attachment"): + attachemnts = self.files.getlist("attachment") + multiple_attachment_ids = [] + for attachemnt in attachemnts: + file_instance = PolicyMultipleFile() + file_instance.attachment = attachemnt + file_instance.save() + multiple_attachment_ids.append(file_instance.pk) + instance = super().save(commit) + if commit: + instance.attachments.add(*multiple_attachment_ids) + return instance, attachemnts diff --git a/employee/models.py b/employee/models.py index 1096ac547..46c2cdd55 100644 --- a/employee/models.py +++ b/employee/models.py @@ -7,6 +7,7 @@ This module is used to register models for employee app import datetime as dtime from datetime import date, datetime import json +from typing import Any from django.conf import settings from django.db import models from django.contrib.auth.models import User, Permission @@ -211,7 +212,7 @@ class Employee(models.Model): """ today = datetime.today() attendance = self.employee_attendances.filter(attendance_date=today).first() - minimum_hour_seconds = strtime_seconds(getattr(attendance,"minimum_hour","0")) + minimum_hour_seconds = strtime_seconds(getattr(attendance, "minimum_hour", "0")) at_work = 0 forecasted_pending_hours = 0 if attendance: @@ -546,3 +547,30 @@ class EmployeeNote(models.Model): def __str__(self) -> str: return f"{self.description}" + + +class PolicyMultipleFile(models.Model): + """ + PoliciesMultipleFile model + """ + + attachment = models.FileField(upload_to="employee/policies") + + +class Policy(models.Model): + """ + Policies model + """ + + title = models.CharField(max_length=50) + body = models.TextField() + is_visible_to_all = models.BooleanField(default=True) + specific_employees = models.ManyToManyField(Employee, blank=True,editable=False) + attachments = models.ManyToManyField(PolicyMultipleFile, blank=True) + company_id = models.ManyToManyField(Company, blank=True) + + objects = HorillaCompanyManager() + + def delete(self, *args, **kwargs): + super().delete(*args, **kwargs) + self.attachments.all().delete() diff --git a/employee/policies.py b/employee/policies.py new file mode 100644 index 000000000..c44cc0d08 --- /dev/null +++ b/employee/policies.py @@ -0,0 +1,127 @@ +""" +policies.py + +This module is used to write operation related to policies +""" + + +from django.contrib import messages +from django.http import HttpResponse +from django.shortcuts import render +from base.views import paginator_qry +from employee.filters import PolicyFilter +from employee.forms import PolicyForm +from employee.models import Policy, PolicyMultipleFile +from horilla.decorators import permission_required, login_required + + +@login_required +def view_policies(request): + """ + Method is used render template to view all the policy records + """ + policies = Policy.objects.all() + if not request.user.has_perm("employee.view_policy"): + policies = policies.filter(is_visible_to_all=True) + return render( + request, + "policies/view_policies.html", + {"policies": paginator_qry(policies, request.GET.get("page"))}, + ) + + +@login_required +@permission_required("employee.add_policy") +def create_policy(request): + """ + Method is used to create/update new policy + """ + instance_id = request.GET.get("instance_id") + instance = None + if isinstance(eval(str(instance_id)), int): + instance = Policy.objects.filter(id=instance_id).first() + form = PolicyForm(instance=instance) + if request.method == "POST": + form = PolicyForm(request.POST, request.FILES, instance=instance) + if form.is_valid(): + form.save() + messages.success(request, "Policy saved") + return HttpResponse("") + return render(request, "policies/form.html", {"form": form}) + + +@login_required +def search_policies(request): + """ + This method is used to search in policies + """ + policies = PolicyFilter(request.GET).qs + if not request.user.has_perm("employee.view_policy"): + policies = policies.filter(is_visible_to_all=True) + return render( + request, + "policies/records.html", + { + "policies": paginator_qry(policies, request.GET.get("page")), + "pd": request.GET.urlencode(), + }, + ) + + +@login_required +def view_policy(request): + """ + This method is used to view the policy + """ + instance_id = request.GET["instance_id"] + policy = Policy.objects.filter(id=instance_id).first() + return render( + request, + "policies/view_policy.html", + { + "policy": policy, + }, + ) + + +@login_required +@permission_required("employee.change_policy") +def add_attachment(request): + """ + This method is used to add attachment to policy + """ + files = request.FILES.getlist("files") + policy_id = request.GET["policy_id"] + attachments = [] + for file in files: + attachment = PolicyMultipleFile() + attachment.attachment = file + attachment.save() + attachments.append(attachment) + policy = Policy.objects.get(id=policy_id) + policy.attachments.add(*attachments) + messages.success(request, "Attachments added") + return render(request, "policies/attachments.html", {"policy": policy}) + + +@login_required +@permission_required("employee.delete_policy") +def remove_attachment(request): + """ + This method is used to remove the attachments + """ + ids = request.GET.getlist("ids") + policy_id = request.GET["policy_id"] + policy = Policy.objects.get(id=policy_id) + PolicyMultipleFile.objects.filter(id__in=ids).delete() + return render(request, "policies/attachments.html", {"policy": policy}) + + +@login_required +def get_attachments(request): + """ + This method is used to view all the attachments inside the policy + """ + policy = request.GET["policy_id"] + policy = Policy.objects.get(id=policy) + return render(request, "policies/attachments.html", {"policy": policy}) diff --git a/employee/templates/policies/attachments.html b/employee/templates/policies/attachments.html new file mode 100644 index 000000000..9d417d89a --- /dev/null +++ b/employee/templates/policies/attachments.html @@ -0,0 +1,26 @@ +{% load static %} +
+ {% for attachment in policy.attachments.all %} + + + + + {% endfor %} + {% if perms.employee.add_policy %} + + + + + {% endif %} +
diff --git a/employee/templates/policies/filter.html b/employee/templates/policies/filter.html new file mode 100644 index 000000000..e69de29bb diff --git a/employee/templates/policies/form.html b/employee/templates/policies/form.html new file mode 100644 index 000000000..ffc0804de --- /dev/null +++ b/employee/templates/policies/form.html @@ -0,0 +1,7 @@ +{% load i18n %} +
+ {{ form.as_p }} +
+ +
+
diff --git a/employee/templates/policies/nav.html b/employee/templates/policies/nav.html new file mode 100644 index 000000000..55fe3d86a --- /dev/null +++ b/employee/templates/policies/nav.html @@ -0,0 +1,34 @@ +{% load i18n %} +
+
+

{% trans 'Policies' %}

+
+ +
+
+ + +
+ {% include 'policies/filter.html' %} + {% if perms.payroll.add_policyaccount %} + + {% endif %} +
+
+ + diff --git a/employee/templates/policies/records.html b/employee/templates/policies/records.html new file mode 100644 index 000000000..cc4691805 --- /dev/null +++ b/employee/templates/policies/records.html @@ -0,0 +1,98 @@ +{% load i18n %} +
+ {% for policy in policies %} +
+

+ + {% if policy.is_visible_to_all %} + + {% else %} + + {% endif %} + {{ policy.title }} +
+ {% if perms.employee.change_policiy %} + + {% endif %} + {% if perms.employee.delete_policy %} + + {% endif %} +
+

+
{{ policy.body|safe }}
+

+ {% if perms.recruitment.change_recruitmentmailtemplate %} + {% trans 'View policy' %} + {% endif %} +
+ {% endfor %} +
+
+
+ {% trans 'Page' %} {{ policies.number }} {% trans 'of' %} {{ policies.paginator.num_pages }}. + + +
+
+ + diff --git a/employee/templates/policies/view_policies.html b/employee/templates/policies/view_policies.html new file mode 100644 index 000000000..1bcf18f1e --- /dev/null +++ b/employee/templates/policies/view_policies.html @@ -0,0 +1,7 @@ +{% extends 'index.html' %} +{% block content %} + {% include 'policies/nav.html' %} +
+ {% include 'policies/records.html' %} +
+{% endblock %} diff --git a/employee/templates/policies/view_policy.html b/employee/templates/policies/view_policy.html new file mode 100644 index 000000000..5defb980d --- /dev/null +++ b/employee/templates/policies/view_policy.html @@ -0,0 +1,15 @@ + +

{{ policy.title }}

+ +{{ policy.body|safe }} +
+ +
diff --git a/employee/urls.py b/employee/urls.py index bb725025c..6f97f86ab 100644 --- a/employee/urls.py +++ b/employee/urls.py @@ -4,7 +4,7 @@ urls.py This module is used to map url path with view methods. """ from django.urls import path -from employee import not_in_out_dashboard, views +from employee import not_in_out_dashboard, policies, views from employee.models import Employee urlpatterns = [ @@ -188,10 +188,22 @@ urlpatterns = [ path("note-tab/", views.note_tab, name="note-tab"), path("add-employee-note//", views.add_note, name="add-employee-note"), path("add-employee-note-post", views.add_note, name="add-employee-note-post"), - path("employee-note-update//", views.employee_note_update, name="employee-note-update"), - path("employee-note-delete//", views.employee_note_delete, name="employee-note-delete"), + path( + "employee-note-update//", + views.employee_note_update, + name="employee-note-update", + ), + path( + "employee-note-delete//", + views.employee_note_delete, + name="employee-note-delete", + ), path("attendance-tab/", views.attendance_tab, name="attendance-tab"), - path("allowances-deductions-tab/", views.allowances_deductions_tab, name="allowances-deductions-tab"), + path( + "allowances-deductions-tab/", + views.allowances_deductions_tab, + name="allowances-deductions-tab", + ), path("shift-tab/", views.shift_tab, name="shift-tab"), path( "contract-tab/", @@ -207,7 +219,26 @@ urlpatterns = [ ), path("not-in-yet/", not_in_out_dashboard.not_in_yet, name="not-in-yet"), path("not-out-yet/", not_in_out_dashboard.not_out_yet, name="not-out-yet"), - path("send-mail//", not_in_out_dashboard.send_mail, name="send-mail-employee"), - path("send-mail", not_in_out_dashboard.send_mail_to_employee, name="send-mail-to-employee"), - path("get-template//", not_in_out_dashboard.get_template, name="get-template-employee"), + path( + "send-mail//", + not_in_out_dashboard.send_mail, + name="send-mail-employee", + ), + path( + "send-mail", + not_in_out_dashboard.send_mail_to_employee, + name="send-mail-to-employee", + ), + path( + "get-template//", + not_in_out_dashboard.get_template, + name="get-template-employee", + ), + path("view-policies", policies.view_policies, name="view-policies"), + path("search-policies", policies.search_policies, name="search-policies"), + path("create-policy", policies.create_policy, name="create-policy"), + path("view-policy", policies.view_policy, name="view-policy"), + path("add-attachment-policy", policies.add_attachment, name="add-attachment-policy"), + path("remove-attachment-policy", policies.remove_attachment, name="remove-attachment-policy"), + path("get-attachments-policy", policies.get_attachments, name="get-attachments-policy"), ]