[UPDT] BASE: Implement history field settings feature

This commit is contained in:
Horilla
2024-02-07 12:55:58 +05:30
parent 28ab005007
commit af72d756be
10 changed files with 298 additions and 137 deletions

View File

@@ -0,0 +1,56 @@
{% load i18n %}
<style>
.select2-selection.select2-selection--multiple {
min-height: 48px;
overflow-y: auto;
}
button {
height: 48px;
}
div[style="display: flex"] {
flex-direction: row;
}
@media (max-width: 768px) {
div[style="display: flex"] {
flex-direction: column;
}
button {
margin-top: 10px;
}
}
</style>
<form
action="{% url 'history-field-settings' %}"
class="settings-label mb-1"
method="post"
>
{% csrf_token %}
<div class="oh-inner-sidebar-content__header mt-4">
<h2 class="oh-inner-sidebar-content__title">
{% trans "Employee History Tracking" %}
</h2>
</div>
<div class="oh-label__info" for="defatul_expire">
<label class="oh-label" for="defatul_expire"
>{% trans "Tracking Fields" %}</label
>
<span class="oh-info mr-2" title="{% trans '' %}"></span>
</div>
<div style="display: flex">
<div style="min-width: 273px">
{{ history_fields_form.tracking_fields }}
</div>
{% if perms.payroll.change_payrollsettings %}
<button
style="display: inline; margin-left: 5px"
type="submit"
class="oh-btn oh-btn--secondary mr-0 oh-btn--w-100-resp"
>
{% trans "Save Changes" %}
</button>
{% endif %}
</div>
<div class="oh-inner-sidebar-content__footer"></div>
</form>

View File

@@ -21,5 +21,5 @@
{% if perms.payroll.change_encashmentgeneralsetting %} {% if perms.payroll.change_encashmentgeneralsetting %}
{% include "settings/encashment_settings.html" %} {% include "settings/encashment_settings.html" %}
{% endif %} {% endif %}
{% include "base/audit_tag/history_tracking_fields.html" %}
{% endblock settings %} {% endblock settings %}

View File

@@ -570,6 +570,7 @@ urlpatterns = [
path("settings/get-date-format/", views.get_date_format, name="get-date-format"), path("settings/get-date-format/", views.get_date_format, name="get-date-format"),
path("settings/save-time/", views.save_time_format, name="save_time_format"), path("settings/save-time/", views.save_time_format, name="save_time_format"),
path("settings/get-time-format/", views.get_time_format, name="get-time-format"), path("settings/get-time-format/", views.get_time_format, name="get-time-format"),
path("history-field-settings",views.history_field_settings,name="history-field-settings"),
path( path(
"settings/attendance-settings-view/", "settings/attendance-settings-view/",
views.validation_condition_view, views.validation_condition_view,

View File

@@ -26,7 +26,8 @@ from attendance.models import AttendanceValidationCondition, GraceTime
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from employee.filters import EmployeeFilter from employee.filters import EmployeeFilter
from employee.forms import ActiontypeForm from employee.forms import ActiontypeForm
from horilla_audit.models import AuditTag from horilla_audit.forms import HistoryTrackingFieldsForm
from horilla_audit.models import AuditTag, HistoryTrackingFields
from notifications.models import Notification from notifications.models import Notification
from notifications.base.models import AbstractNotification from notifications.base.models import AbstractNotification
from notifications.signals import notify from notifications.signals import notify
@@ -3750,6 +3751,13 @@ def general_settings(request):
form = AnnouncementExpireForm(instance=instance) form = AnnouncementExpireForm(instance=instance)
encashment_instance = EncashmentGeneralSettings.objects.first() encashment_instance = EncashmentGeneralSettings.objects.first()
encashment_form = EncashmentGeneralSettingsForm(instance=encashment_instance) encashment_form = EncashmentGeneralSettingsForm(instance=encashment_instance)
history_tracking_instance = HistoryTrackingFields.objects.first()
history_fields_form_initial = {}
if history_tracking_instance:
history_fields_form_initial = {
"tracking_fields": history_tracking_instance.tracking_fields['tracking_fields']
}
history_fields_form = HistoryTrackingFieldsForm(initial = history_fields_form_initial)
if request.method == "POST": if request.method == "POST":
form = AnnouncementExpireForm(request.POST, instance=instance) form = AnnouncementExpireForm(request.POST, instance=instance)
if form.is_valid(): if form.is_valid():
@@ -3762,6 +3770,7 @@ def general_settings(request):
{ {
"form": form, "form": form,
"encashment_form": encashment_form, "encashment_form": encashment_form,
"history_fields_form":history_fields_form,
}, },
) )
@@ -3898,6 +3907,20 @@ def get_time_format(request):
# Return the date format as JSON response # Return the date format as JSON response
return JsonResponse({"selected_format": time_format}) return JsonResponse({"selected_format": time_format})
@login_required
def history_field_settings(request):
if request.method == "POST":
fields = request.POST.getlist("tracking_fields")
history_object, created = HistoryTrackingFields.objects.get_or_create(
pk=1, defaults={"tracking_fields": {"tracking_fields": fields}}
)
if not created:
history_object.tracking_fields = {"tracking_fields": fields}
history_object.save()
return redirect(general_settings)
@login_required @login_required
@permission_required("attendance.view_attendancevalidationcondition") @permission_required("attendance.view_attendancevalidationcondition")

View File

@@ -434,10 +434,20 @@ class EmployeeWorkInformation(models.Model):
null=True, null=True,
verbose_name=_("Company"), verbose_name=_("Company"),
) )
tags = models.ManyToManyField(EmployeeTag, blank=True, verbose_name=_("tags")) tags = models.ManyToManyField(
location = models.CharField(max_length=50, blank=True) EmployeeTag, blank=True, verbose_name=_("Employee tag")
email = models.EmailField(max_length=254, blank=True, null=True) )
mobile = models.CharField(max_length=254, blank=True, null=True) location = models.CharField(
max_length=50, blank=True, verbose_name=_("Work Location")
)
email = models.EmailField(
max_length=254, blank=True, null=True, verbose_name=_("Email")
)
mobile = models.CharField(
max_length=254,
blank=True,
null=True,
)
shift_id = models.ForeignKey( shift_id = models.ForeignKey(
EmployeeShift, EmployeeShift,
on_delete=models.DO_NOTHING, on_delete=models.DO_NOTHING,
@@ -445,10 +455,16 @@ class EmployeeWorkInformation(models.Model):
blank=True, blank=True,
verbose_name=_("Shift"), verbose_name=_("Shift"),
) )
date_joining = models.DateField(null=True, blank=True) date_joining = models.DateField(
null=True, blank=True, verbose_name=_("Joining Date")
)
contract_end_date = models.DateField(blank=True, null=True) contract_end_date = models.DateField(blank=True, null=True)
basic_salary = models.IntegerField(null=True, blank=True, default=0) basic_salary = models.IntegerField(
salary_hour = models.IntegerField(null=True, blank=True, default=0) null=True, blank=True, default=0, verbose_name=_("Basic Salary")
)
salary_hour = models.IntegerField(
null=True, blank=True, default=0, verbose_name=_("Salary Per Hour")
)
additional_info = models.JSONField(null=True, blank=True) additional_info = models.JSONField(null=True, blank=True)
experience = models.FloatField(null=True, blank=True, default=0) experience = models.FloatField(null=True, blank=True, default=0)
history = HorillaAuditLog( history = HorillaAuditLog(

View File

@@ -7,19 +7,19 @@ var downloadMessages = {
}; };
var importSuccess = { var importSuccess = {
ar: "نجح الاستيراد", ar: "نجح الاستيراد", // Arabic
de: "Import erfolgreich", de: "Import erfolgreich", // German
es: "Importado con éxito", es: "Importado con éxito", // Spanish
en: "Imported Successfully!", en: "Imported Successfully!", // English
fr: "Importation réussie", fr: "Importation réussie", // French
}; };
var uploadSuccess = { var uploadSuccess = {
ar: "تحميل كامل", ar: "تحميل كامل", // Arabic
de: "Upload abgeschlossen", de: "Upload abgeschlossen", // German
es: "Carga completa", es: "Carga completa", // Spanish
en: "Upload Complete!", en: "Upload Complete!", // English
fr: "Téléchargement terminé", fr: "Téléchargement terminé", // French
}; };
var uploadingMessage = { var uploadingMessage = {
@@ -55,14 +55,28 @@ function getCookie(name) {
} }
function getCurrentLanguageCode(callback) { function getCurrentLanguageCode(callback) {
var languageCode = $("#main-section-data").attr("data-lang");
var allowedLanguageCodes = ["ar", "de", "es", "en", "fr"];
if (allowedLanguageCodes.includes(languageCode)) {
callback(languageCode);
} else {
$.ajax({ $.ajax({
type: "GET", type: "GET",
url: "/employee/get-language-code/", url: "/employee/get-language-code/",
success: function (response) { success: function (response) {
var languageCode = response.language_code; var ajaxLanguageCode = response.language_code;
callback(languageCode); // Pass the language code to the callback $("#main-section-data").attr("data-lang", ajaxLanguageCode);
callback(
allowedLanguageCodes.includes(ajaxLanguageCode)
? ajaxLanguageCode
: "en"
);
},
error: function () {
callback("en");
}, },
}); });
}
} }
// Get the form element // Get the form element
@@ -112,10 +126,9 @@ form.addEventListener("submit", function (event) {
$("#work-info-import").click(function (e) { $("#work-info-import").click(function (e) {
e.preventDefault(); e.preventDefault();
var languageCode = null; var languageCode = null;
languageCode = $("#main-section-data").attr("data-lang"); getCurrentLanguageCode(function (code) {
var confirmMessage = languageCode = code;
downloadMessages[languageCode] || var confirmMessage = downloadMessages[languageCode];
((languageCode = "en"), downloadMessages[languageCode]);
Swal.fire({ Swal.fire({
text: confirmMessage, text: confirmMessage,
icon: "question", icon: "question",
@@ -137,7 +150,9 @@ $("#work-info-import").click(function (e) {
$(".progress-bar") $(".progress-bar")
.width(percent + "%") .width(percent + "%")
.attr("aria-valuenow", percent); .attr("aria-valuenow", percent);
$("#progress-text").text("Uploading... " + percent.toFixed(2) + "%"); $("#progress-text").text(
"Uploading... " + percent.toFixed(2) + "%"
);
} }
}; };
@@ -162,6 +177,7 @@ $("#work-info-import").click(function (e) {
xhr.send(); xhr.send();
} }
}); });
});
}); });
$(document).ajaxStart(function () { $(document).ajaxStart(function () {
@@ -173,17 +189,10 @@ $(document).ajaxStop(function () {
}); });
function simulateProgress() { function simulateProgress() {
var languageCode = null; getCurrentLanguageCode(function (code) {
languageCode = $("#main-section-data").attr("data-lang"); languageCode = code;
var importMessage = var importMessage = importSuccess[languageCode];
importSuccess[languageCode] || var uploadMessage = uploadSuccess[languageCode];
((languageCode = "en"), importSuccess[languageCode]);
var uploadMessage =
uploadSuccess[languageCode] ||
((languageCode = "en"), uploadSuccess[languageCode]);
var uploadingMessage =
uploadingMessage[languageCode] ||
((languageCode = "en"), uploadingMessage[languageCode]);
let progressBar = document.querySelector(".progress-bar"); let progressBar = document.querySelector(".progress-bar");
let progressText = document.getElementById("progress-text"); let progressText = document.getElementById("progress-text");
@@ -210,20 +219,20 @@ function simulateProgress() {
width++; width++;
progressBar.style.width = width + "%"; progressBar.style.width = width + "%";
progressBar.setAttribute("aria-valuenow", width); progressBar.setAttribute("aria-valuenow", width);
progressText.innerText = uploadingMessage + width + "%"; progressText.innerText = uploadingMessage[languageCode] + width + "%";
} }
}, 20); }, 20);
});
} }
document document
.getElementById("workInfoImportForm") .getElementById("workInfoImportForm")
.addEventListener("submit", function (event) { .addEventListener("submit", function (event) {
event.preventDefault(); event.preventDefault();
var languageCode = null; getCurrentLanguageCode(function (code) {
languageCode = $("#main-section-data").attr("data-lang"); languageCode = code;
var errorMessage = var errorMessage = validationMessage[languageCode];
validationMessage[languageCode] ||
((languageCode = "en"), validationMessage[languageCode]);
var fileInput = $("#workInfoImportFile").val(); var fileInput = $("#workInfoImportFile").val();
var allowedExtensions = /(\.xlsx)$/i; var allowedExtensions = /(\.xlsx)$/i;
@@ -248,3 +257,4 @@ document
simulateProgress(); simulateProgress();
} }
}); });
});

View File

@@ -7,8 +7,7 @@
<div class="col-sm-12 col-md-12 col-lg-6"> <div class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group"> <div class="oh-input-group">
<label class="oh-label"> <label class="oh-label">
<input type="checkbox" id="select-all-fields" /> {% trans "Select <input type="checkbox" id="select-all-fields" /> {% trans "Select All" %}
All" %}
</label> </label>
</div> </div>
</div> </div>

View File

@@ -24,10 +24,11 @@ class HistoryForm(forms.Form):
required=False, required=False,
label=_("Updation description"), label=_("Updation description"),
) )
history_highlight = forms.BooleanField(required=False,label=_("Updation highlight")) history_highlight = forms.BooleanField(
required=False, label=_("Updation highlight")
)
history_tags = forms.ModelMultipleChoiceField( history_tags = forms.ModelMultipleChoiceField(
queryset=AuditTag.objects.all(), required=False, queryset=AuditTag.objects.all(), required=False, label=_("Updation tag")
label = _("Updation tag")
) )
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
@@ -50,3 +51,33 @@ class HistoryForm(forms.Form):
context = {"form": self} context = {"form": self}
table_html = render_to_string("horilla_audit/horilla_audit_log.html", context) table_html = render_to_string("horilla_audit/horilla_audit_log.html", context)
return table_html return table_html
class HistoryTrackingFieldsForm(forms.Form):
excluded_fields = [
"id",
"employee_id",
"objects",
"mobile",
"contract_end_date",
"additional_info",
"experience",
]
def __init__(self, *args, **kwargs):
from employee.models import EmployeeWorkInformation as model
super(HistoryTrackingFieldsForm, self).__init__(*args, **kwargs)
field_choices = [
(field.name, field.verbose_name)
for field in model._meta.get_fields()
if hasattr(field, "verbose_name") and field.name not in self.excluded_fields
]
self.fields["tracking_fields"] = forms.MultipleChoiceField(
choices=field_choices,
required=False,
widget=forms.SelectMultiple(
attrs={
"class": "oh-select oh-select-2 select2-hidden-accessible",
"style": "height:270px;",
}
),
)

View File

@@ -6,7 +6,6 @@ This module is used to write methods related to the history
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
class Bot: class Bot:
def __init__(self) -> None: def __init__(self) -> None:
self.__str__() self.__str__()
@@ -54,6 +53,21 @@ def get_field_label(model_class, field_name):
return None return None
def filter_history(histories,track_fields):
filtered_histories = []
for history in histories:
changes = history.get("changes", [])
filtered_changes = [
change
for change in changes
if change.get("field_name", "") in track_fields
]
if filtered_changes:
history["changes"] = filtered_changes
filtered_histories.append(history)
histories = filtered_histories
return histories
def get_diff(instance): def get_diff(instance):
""" """
This method is used to find the differences in the history This method is used to find the differences in the history
@@ -117,4 +131,10 @@ def get_diff(instance):
"updated_by": updated_by, "updated_by": updated_by,
} }
) )
from .models import HistoryTrackingFields
history_tracking_instance = HistoryTrackingFields.objects.first()
if history_tracking_instance:
track_fields = history_tracking_instance.tracking_fields["tracking_fields"]
if track_fields:
delta_changes = filter_history(delta_changes,track_fields)
return delta_changes return delta_changes

View File

@@ -1,6 +1,7 @@
""" """
models.py models.py
""" """
from collections.abc import Iterable from collections.abc import Iterable
from django.db import models from django.db import models
from django.dispatch import receiver from django.dispatch import receiver
@@ -119,6 +120,10 @@ def post_create_horilla_audit_log(sender, instance, *_args, **kwargs):
pass pass
class HistoryTrackingFields(models.Model):
tracking_fields = models.JSONField(null=True, blank=True, editable=False)
# class HistoryComment(models.Model): # class HistoryComment(models.Model):
# """ # """
# HistoryComment model # HistoryComment model