[UPDT] BASE: Implement history field settings feature
This commit is contained in:
56
base/templates/base/audit_tag/history_tracking_fields.html
Normal file
56
base/templates/base/audit_tag/history_tracking_fields.html
Normal 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>
|
||||
@@ -21,5 +21,5 @@
|
||||
{% if perms.payroll.change_encashmentgeneralsetting %}
|
||||
{% include "settings/encashment_settings.html" %}
|
||||
{% endif %}
|
||||
|
||||
{% include "base/audit_tag/history_tracking_fields.html" %}
|
||||
{% endblock settings %}
|
||||
@@ -570,6 +570,7 @@ urlpatterns = [
|
||||
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/get-time-format/", views.get_time_format, name="get-time-format"),
|
||||
path("history-field-settings",views.history_field_settings,name="history-field-settings"),
|
||||
path(
|
||||
"settings/attendance-settings-view/",
|
||||
views.validation_condition_view,
|
||||
|
||||
@@ -26,7 +26,8 @@ from attendance.models import AttendanceValidationCondition, GraceTime
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from employee.filters import EmployeeFilter
|
||||
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.base.models import AbstractNotification
|
||||
from notifications.signals import notify
|
||||
@@ -3750,6 +3751,13 @@ def general_settings(request):
|
||||
form = AnnouncementExpireForm(instance=instance)
|
||||
encashment_instance = EncashmentGeneralSettings.objects.first()
|
||||
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":
|
||||
form = AnnouncementExpireForm(request.POST, instance=instance)
|
||||
if form.is_valid():
|
||||
@@ -3762,6 +3770,7 @@ def general_settings(request):
|
||||
{
|
||||
"form": 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 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
|
||||
@permission_required("attendance.view_attendancevalidationcondition")
|
||||
|
||||
@@ -434,10 +434,20 @@ class EmployeeWorkInformation(models.Model):
|
||||
null=True,
|
||||
verbose_name=_("Company"),
|
||||
)
|
||||
tags = models.ManyToManyField(EmployeeTag, blank=True, verbose_name=_("tags"))
|
||||
location = models.CharField(max_length=50, blank=True)
|
||||
email = models.EmailField(max_length=254, blank=True, null=True)
|
||||
mobile = models.CharField(max_length=254, blank=True, null=True)
|
||||
tags = models.ManyToManyField(
|
||||
EmployeeTag, blank=True, verbose_name=_("Employee tag")
|
||||
)
|
||||
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(
|
||||
EmployeeShift,
|
||||
on_delete=models.DO_NOTHING,
|
||||
@@ -445,10 +455,16 @@ class EmployeeWorkInformation(models.Model):
|
||||
blank=True,
|
||||
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)
|
||||
basic_salary = models.IntegerField(null=True, blank=True, default=0)
|
||||
salary_hour = models.IntegerField(null=True, blank=True, default=0)
|
||||
basic_salary = models.IntegerField(
|
||||
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)
|
||||
experience = models.FloatField(null=True, blank=True, default=0)
|
||||
history = HorillaAuditLog(
|
||||
|
||||
@@ -7,19 +7,19 @@ var downloadMessages = {
|
||||
};
|
||||
|
||||
var importSuccess = {
|
||||
ar: "نجح الاستيراد",
|
||||
de: "Import erfolgreich",
|
||||
es: "Importado con éxito",
|
||||
en: "Imported Successfully!",
|
||||
fr: "Importation réussie",
|
||||
ar: "نجح الاستيراد", // Arabic
|
||||
de: "Import erfolgreich", // German
|
||||
es: "Importado con éxito", // Spanish
|
||||
en: "Imported Successfully!", // English
|
||||
fr: "Importation réussie", // French
|
||||
};
|
||||
|
||||
var uploadSuccess = {
|
||||
ar: "تحميل كامل",
|
||||
de: "Upload abgeschlossen",
|
||||
es: "Carga completa",
|
||||
en: "Upload Complete!",
|
||||
fr: "Téléchargement terminé",
|
||||
ar: "تحميل كامل", // Arabic
|
||||
de: "Upload abgeschlossen", // German
|
||||
es: "Carga completa", // Spanish
|
||||
en: "Upload Complete!", // English
|
||||
fr: "Téléchargement terminé", // French
|
||||
};
|
||||
|
||||
var uploadingMessage = {
|
||||
@@ -55,14 +55,28 @@ function getCookie(name) {
|
||||
}
|
||||
|
||||
function getCurrentLanguageCode(callback) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/employee/get-language-code/",
|
||||
success: function (response) {
|
||||
var languageCode = response.language_code;
|
||||
callback(languageCode); // Pass the language code to the callback
|
||||
},
|
||||
});
|
||||
var languageCode = $("#main-section-data").attr("data-lang");
|
||||
var allowedLanguageCodes = ["ar", "de", "es", "en", "fr"];
|
||||
if (allowedLanguageCodes.includes(languageCode)) {
|
||||
callback(languageCode);
|
||||
} else {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/employee/get-language-code/",
|
||||
success: function (response) {
|
||||
var ajaxLanguageCode = response.language_code;
|
||||
$("#main-section-data").attr("data-lang", ajaxLanguageCode);
|
||||
callback(
|
||||
allowedLanguageCodes.includes(ajaxLanguageCode)
|
||||
? ajaxLanguageCode
|
||||
: "en"
|
||||
);
|
||||
},
|
||||
error: function () {
|
||||
callback("en");
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get the form element
|
||||
@@ -112,55 +126,57 @@ form.addEventListener("submit", function (event) {
|
||||
$("#work-info-import").click(function (e) {
|
||||
e.preventDefault();
|
||||
var languageCode = null;
|
||||
languageCode = $("#main-section-data").attr("data-lang");
|
||||
var confirmMessage =
|
||||
downloadMessages[languageCode] ||
|
||||
((languageCode = "en"), downloadMessages[languageCode]);
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
$("#loading").show();
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var confirmMessage = downloadMessages[languageCode];
|
||||
Swal.fire({
|
||||
text: confirmMessage,
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#008000",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm",
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
$("#loading").show();
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/employee/work-info-import", true);
|
||||
xhr.responseType = "arraybuffer";
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/employee/work-info-import", true);
|
||||
xhr.responseType = "arraybuffer";
|
||||
|
||||
xhr.upload.onprogress = function (e) {
|
||||
if (e.lengthComputable) {
|
||||
var percent = (e.loaded / e.total) * 100;
|
||||
$(".progress-bar")
|
||||
.width(percent + "%")
|
||||
.attr("aria-valuenow", percent);
|
||||
$("#progress-text").text("Uploading... " + percent.toFixed(2) + "%");
|
||||
}
|
||||
};
|
||||
xhr.upload.onprogress = function (e) {
|
||||
if (e.lengthComputable) {
|
||||
var percent = (e.loaded / e.total) * 100;
|
||||
$(".progress-bar")
|
||||
.width(percent + "%")
|
||||
.attr("aria-valuenow", percent);
|
||||
$("#progress-text").text(
|
||||
"Uploading... " + percent.toFixed(2) + "%"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = function (e) {
|
||||
if (this.status == 200) {
|
||||
const file = new Blob([this.response], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
const url = URL.createObjectURL(file);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = "work_info_template.xlsx";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
}
|
||||
};
|
||||
xhr.onload = function (e) {
|
||||
if (this.status == 200) {
|
||||
const file = new Blob([this.response], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
const url = URL.createObjectURL(file);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = "work_info_template.xlsx";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function (e) {
|
||||
console.error("Error downloading file:", e);
|
||||
};
|
||||
xhr.onerror = function (e) {
|
||||
console.error("Error downloading file:", e);
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
xhr.send();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -173,78 +189,72 @@ $(document).ajaxStop(function () {
|
||||
});
|
||||
|
||||
function simulateProgress() {
|
||||
var languageCode = null;
|
||||
languageCode = $("#main-section-data").attr("data-lang");
|
||||
var importMessage =
|
||||
importSuccess[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 progressText = document.getElementById("progress-text");
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var importMessage = importSuccess[languageCode];
|
||||
var uploadMessage = uploadSuccess[languageCode];
|
||||
let progressBar = document.querySelector(".progress-bar");
|
||||
let progressText = document.getElementById("progress-text");
|
||||
|
||||
let width = 0;
|
||||
let interval = setInterval(function () {
|
||||
if (width >= 100) {
|
||||
clearInterval(interval);
|
||||
progressText.innerText = uploadMessage;
|
||||
setTimeout(function () {
|
||||
document.getElementById("loading").style.display = "none";
|
||||
}, 3000);
|
||||
Swal.fire({
|
||||
text: importMessage,
|
||||
icon: "success",
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: true,
|
||||
});
|
||||
setTimeout(function () {
|
||||
$("#workInfoImport").removeClass("oh-modal--show");
|
||||
location.reload(true);
|
||||
}, 2000);
|
||||
} else {
|
||||
width++;
|
||||
progressBar.style.width = width + "%";
|
||||
progressBar.setAttribute("aria-valuenow", width);
|
||||
progressText.innerText = uploadingMessage + width + "%";
|
||||
}
|
||||
}, 20);
|
||||
let width = 0;
|
||||
let interval = setInterval(function () {
|
||||
if (width >= 100) {
|
||||
clearInterval(interval);
|
||||
progressText.innerText = uploadMessage;
|
||||
setTimeout(function () {
|
||||
document.getElementById("loading").style.display = "none";
|
||||
}, 3000);
|
||||
Swal.fire({
|
||||
text: importMessage,
|
||||
icon: "success",
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: true,
|
||||
});
|
||||
setTimeout(function () {
|
||||
$("#workInfoImport").removeClass("oh-modal--show");
|
||||
location.reload(true);
|
||||
}, 2000);
|
||||
} else {
|
||||
width++;
|
||||
progressBar.style.width = width + "%";
|
||||
progressBar.setAttribute("aria-valuenow", width);
|
||||
progressText.innerText = uploadingMessage[languageCode] + width + "%";
|
||||
}
|
||||
}, 20);
|
||||
});
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById("workInfoImportForm")
|
||||
.addEventListener("submit", function (event) {
|
||||
event.preventDefault();
|
||||
var languageCode = null;
|
||||
languageCode = $("#main-section-data").attr("data-lang");
|
||||
var errorMessage =
|
||||
validationMessage[languageCode] ||
|
||||
((languageCode = "en"), validationMessage[languageCode]);
|
||||
var fileInput = $("#workInfoImportFile").val();
|
||||
var allowedExtensions = /(\.xlsx)$/i;
|
||||
getCurrentLanguageCode(function (code) {
|
||||
languageCode = code;
|
||||
var errorMessage = validationMessage[languageCode];
|
||||
|
||||
if (!allowedExtensions.exec(fileInput)) {
|
||||
var errorMessage = document.createElement("div");
|
||||
errorMessage.classList.add("error-message");
|
||||
var fileInput = $("#workInfoImportFile").val();
|
||||
var allowedExtensions = /(\.xlsx)$/i;
|
||||
|
||||
errorMessage.textContent = errorMessage;
|
||||
if (!allowedExtensions.exec(fileInput)) {
|
||||
var errorMessage = document.createElement("div");
|
||||
errorMessage.classList.add("error-message");
|
||||
|
||||
document.getElementById("error-container").appendChild(errorMessage);
|
||||
errorMessage.textContent = errorMessage;
|
||||
|
||||
fileInput.value = "";
|
||||
document.getElementById("error-container").appendChild(errorMessage);
|
||||
|
||||
setTimeout(function () {
|
||||
errorMessage.remove();
|
||||
}, 2000);
|
||||
fileInput.value = "";
|
||||
|
||||
return false;
|
||||
} else {
|
||||
document.getElementById("loading").style.display = "block";
|
||||
setTimeout(function () {
|
||||
errorMessage.remove();
|
||||
}, 2000);
|
||||
|
||||
simulateProgress();
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
document.getElementById("loading").style.display = "block";
|
||||
|
||||
simulateProgress();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">
|
||||
<input type="checkbox" id="select-all-fields" /> {% trans "Select
|
||||
All" %}
|
||||
<input type="checkbox" id="select-all-fields" /> {% trans "Select All" %}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,10 +24,11 @@ class HistoryForm(forms.Form):
|
||||
required=False,
|
||||
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(
|
||||
queryset=AuditTag.objects.all(), required=False,
|
||||
label = _("Updation tag")
|
||||
queryset=AuditTag.objects.all(), required=False, label=_("Updation tag")
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
@@ -50,3 +51,33 @@ class HistoryForm(forms.Form):
|
||||
context = {"form": self}
|
||||
table_html = render_to_string("horilla_audit/horilla_audit_log.html", context)
|
||||
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;",
|
||||
}
|
||||
),
|
||||
)
|
||||
@@ -6,7 +6,6 @@ This module is used to write methods related to the history
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
class Bot:
|
||||
def __init__(self) -> None:
|
||||
self.__str__()
|
||||
@@ -54,6 +53,21 @@ def get_field_label(model_class, field_name):
|
||||
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):
|
||||
"""
|
||||
This method is used to find the differences in the history
|
||||
@@ -117,4 +131,10 @@ def get_diff(instance):
|
||||
"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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
models.py
|
||||
"""
|
||||
|
||||
from collections.abc import Iterable
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
@@ -119,6 +120,10 @@ def post_create_horilla_audit_log(sender, instance, *_args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class HistoryTrackingFields(models.Model):
|
||||
tracking_fields = models.JSONField(null=True, blank=True, editable=False)
|
||||
|
||||
|
||||
# class HistoryComment(models.Model):
|
||||
# """
|
||||
# HistoryComment model
|
||||
|
||||
Reference in New Issue
Block a user