[UPDT] EMPLOYEE: Updated employee import method

This commit is contained in:
Horilla
2025-04-11 10:28:03 +05:30
parent 227f8d7ef6
commit 603760a4a4
8 changed files with 990 additions and 1131 deletions

View File

@@ -492,8 +492,11 @@ excel_columns = [
("employee_work_info__work_type_id", trans("Work Type")), ("employee_work_info__work_type_id", trans("Work Type")),
("employee_work_info__reporting_manager_id", trans("Reporting Manager")), ("employee_work_info__reporting_manager_id", trans("Reporting Manager")),
("employee_work_info__employee_type_id", trans("Employee Type")), ("employee_work_info__employee_type_id", trans("Employee Type")),
("employee_work_info__location", trans("Work Location")), ("employee_work_info__location", trans("Location")),
("employee_work_info__date_joining", trans("Date Joining")), ("employee_work_info__date_joining", trans("Date Joining")),
("employee_work_info__basic_salary", trans("Basic Salary")),
("employee_work_info__salary_hour", trans("Salary Hour")),
("employee_work_info__contract_end_date", trans("Contract End Date")),
("employee_work_info__company_id", trans("Company")), ("employee_work_info__company_id", trans("Company")),
("employee_bank_details__bank_name", trans("Bank Name")), ("employee_bank_details__bank_name", trans("Bank Name")),
("employee_bank_details__branch", trans("Branch")), ("employee_bank_details__branch", trans("Branch")),

View File

@@ -3,14 +3,16 @@ employee/methods.py
""" """
import logging import logging
import re
import threading import threading
from datetime import datetime from datetime import date, datetime
from itertools import groupby from itertools import groupby
import pandas as pd import pandas as pd
from django.apps import apps from django.apps import apps
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.utils.translation import gettext as _
from base.context_processors import get_initial_prefix from base.context_processors import get_initial_prefix
from base.models import ( from base.models import (
@@ -26,6 +28,72 @@ from employee.models import Employee, EmployeeWorkInformation
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
error_data_template = {
field: []
for field in [
"Badge ID",
"First Name",
"Last Name",
"Phone",
"Email",
"Gender",
"Department",
"Job Position",
"Job Role",
"Work Type",
"Shift",
"Employee Type",
"Reporting Manager",
"Company",
"Location",
"Date Joining",
"Contract End Date",
"Basic Salary",
"Salary Hour",
"Email Error",
"First Name Error",
"Name and Email Error",
"Phone Error",
"Gender Error",
"Joining Date Error",
"Contract Date Error",
"Badge ID Error",
"Basic Salary Error",
"Salary Hour Error",
"User ID Error",
"Company Error",
]
}
def normalize_phone(phone):
phone = str(phone).strip()
if phone.startswith("+"):
return "+" + re.sub(r"\D", "", phone[1:])
return re.sub(r"\D", "", phone)
def import_valid_date(date_value, field_label, errors_dict, error_key):
if pd.isna(date_value) or date_value is None or str(date_value).strip() == "":
return None
if isinstance(date_value, datetime):
return date_value.date()
date_str = str(date_value).strip()
date_formats = ["%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y"]
for fmt in date_formats:
try:
return datetime.strptime(date_str, fmt).date()
except ValueError:
continue
errors_dict[error_key] = (
f"{field_label} is not a valid date. Expected formats: YYYY-MM-DD, DD/MM/YYYY"
)
return None
def convert_nan(field, dicts): def convert_nan(field, dicts):
""" """
@@ -117,6 +185,172 @@ def check_relationship_with_employee_model(model):
return related_fields return related_fields
def valid_import_file_headers(data_frame):
if data_frame.empty:
message = _("The uploaded file is empty, Not contain records.")
return False, message
required_keys = [
"Badge ID",
"First Name",
"Last Name",
"Phone",
"Email",
"Gender",
"Department",
"Job Position",
"Job Role",
"Work Type",
"Shift",
"Employee Type",
"Reporting Manager",
"Company",
"Location",
"Date Joining",
"Contract End Date",
"Basic Salary",
"Salary Hour",
]
missing_keys = [key for key in required_keys if key not in data_frame.columns]
if missing_keys:
message = _(
"These required headers are missing in the uploaded file: "
) + ", ".join(missing_keys)
return False, message
return True, ""
def process_employee_records(data_frame):
created_count = 0
success_list, error_list = [], []
employee_dicts = data_frame.to_dict("records")
email_regex = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
phone_regex = re.compile(r"^\+?\d{10,15}$")
allowed_genders = {choice[0] for choice in Employee.choice_gender}
existing_badge_ids = set(Employee.objects.values_list("badge_id", flat=True))
existing_usernames = set(User.objects.values_list("username", flat=True))
existing_name_emails = set(
Employee.objects.values_list(
"employee_first_name", "employee_last_name", "email"
)
)
existing_companies = set(Company.objects.values_list("company", flat=True))
for emp in employee_dicts:
errors, save = {}, True
email = emp.get("Email", "").strip()
raw_phone = emp.get("Phone", "")
phone = normalize_phone(raw_phone)
badge_id = str(emp.get("Badge ID", "") or "").strip()
first_name = convert_nan("First Name", emp)
last_name = convert_nan("Last Name", emp)
gender = emp.get("Gender", "").strip().lower()
company = convert_nan("Company", emp)
basic_salary = convert_nan("Basic Salary", emp)
salary_hour = convert_nan("Salary Hour", emp)
joining_date = import_valid_date(
emp.get("Date Joining"), "Joining Date", errors, "Joining Date Error"
)
if "Joining Date Error" in errors:
save = False
if joining_date and joining_date > date.today():
errors["Joining Date Error"] = "Joining date cannot be in the future."
save = False
contract_end_date = import_valid_date(
emp.get("Contract End Date"),
"Contract End Date",
errors,
"Contract Date Error",
)
if "Contract Error" in errors:
save = False
if contract_end_date and joining_date and contract_end_date < joining_date:
errors["Contract Date Error"] = (
"Contract end date cannot be before joining date."
)
save = False
if not email or not email_regex.match(email):
errors["Email Error"] = "Invalid email address."
save = False
if not first_name:
errors["First Name Error"] = "First name cannot be empty."
save = False
if not phone_regex.match(phone):
errors["Phone Error"] = "Invalid phone number format."
save = False
if badge_id in existing_badge_ids:
errors["Badge ID Error"] = "An employee with this badge ID already exists."
save = False
else:
existing_badge_ids.add(badge_id)
if email in existing_usernames:
errors["User ID Error"] = "User with this email already exists."
save = False
else:
existing_usernames.add(email)
name_email_tuple = (first_name, last_name, email)
if name_email_tuple in existing_name_emails:
errors["Name and Email Error"] = (
"This employee already exists in the system."
)
save = False
else:
existing_name_emails.add(name_email_tuple)
if gender and gender not in allowed_genders:
errors["Gender Error"] = (
f"Invalid gender. Allowed values: {', '.join(allowed_genders)}."
)
save = False
if company and company not in existing_companies:
errors["Company Error"] = f"Company '{company}' does not exist."
save = False
if basic_salary not in [None, ""]:
try:
basic_salary_val = float(basic_salary)
if basic_salary_val <= 0:
raise ValueError
except (ValueError, TypeError):
errors["Basic Salary Error"] = "Basic salary must be a positive number."
save = False
if salary_hour not in [None, ""]:
try:
salary_hour_val = float(salary_hour)
if salary_hour_val < 0:
raise ValueError
except (ValueError, TypeError):
errors["Salary Hour Error"] = (
"Salary hour must be a non-negative number."
)
save = False
if save:
emp["Phone"] = phone
emp["Date Joining"] = joining_date
emp["Contract End Date"] = contract_end_date
success_list.append(emp)
created_count += 1
else:
emp.update(errors)
error_list.append(emp)
return success_list, error_list, created_count
def bulk_create_user_import(success_lists): def bulk_create_user_import(success_lists):
""" """
Bulk creation of user instances based on the excel import of employees Bulk creation of user instances based on the excel import of employees
@@ -166,7 +400,7 @@ def bulk_create_employee_import(success_lists):
if not user: if not user:
continue continue
badge_id = work_info["Badge id"] badge_id = work_info["Badge ID"]
first_name = convert_nan("First Name", work_info) first_name = convert_nan("First Name", work_info)
last_name = convert_nan("Last Name", work_info) last_name = convert_nan("Last Name", work_info)
phone = work_info["Phone"] phone = work_info["Phone"]
@@ -203,7 +437,7 @@ def set_initial_password(employees):
logger.info("initial password configured") logger.info("initial password configured")
def optimize_reporting_manager_lookup(success_lists): def optimize_reporting_manager_lookup():
""" """
Optimizes the lookup of reporting managers from a list of work information. Optimizes the lookup of reporting managers from a list of work information.
@@ -212,21 +446,8 @@ def optimize_reporting_manager_lookup(success_lists):
single database query, and creates a dictionary for quick lookups based single database query, and creates a dictionary for quick lookups based
on the full name of the reporting managers. on the full name of the reporting managers.
""" """
# Step 1: Collect unique reporting manager names employees = Employee.objects.entire()
unique_managers = set()
for work_info in success_lists:
reporting_manager = convert_nan("Reporting Manager", work_info)
if isinstance(reporting_manager, str) and " " in reporting_manager:
unique_managers.add(reporting_manager)
# Step 2: Query all relevant Employee objects in one go
manager_names = list(unique_managers)
employees = Employee.objects.filter(
employee_first_name__in=[name.split(" ")[0] for name in manager_names],
employee_last_name__in=[name.split(" ")[1] for name in manager_names],
)
# Step 3: Create a dictionary for quick lookups
employee_dict = { employee_dict = {
f"{employee.employee_first_name} {employee.employee_last_name}": employee f"{employee.employee_first_name} {employee.employee_last_name}": employee
for employee in employees for employee in employees
@@ -434,8 +655,7 @@ def bulk_create_work_info_import(success_lists):
new_work_info_list = [] new_work_info_list = []
update_work_info_list = [] update_work_info_list = []
# Filtered data for required lookups badge_ids = [row["Badge ID"] for row in success_lists]
badge_ids = [row["Badge id"] for row in success_lists]
departments = set(row.get("Department") for row in success_lists) departments = set(row.get("Department") for row in success_lists)
job_positions = set(row.get("Job Position") for row in success_lists) job_positions = set(row.get("Job Position") for row in success_lists)
job_roles = set(row.get("Job Role") for row in success_lists) job_roles = set(row.get("Job Role") for row in success_lists)
@@ -444,7 +664,6 @@ def bulk_create_work_info_import(success_lists):
shifts = set(row.get("Shift") for row in success_lists) shifts = set(row.get("Shift") for row in success_lists)
companies = set(row.get("Company") for row in success_lists) companies = set(row.get("Company") for row in success_lists)
# Bulk fetch related objects and reduce repeated DB calls
existing_employees = { existing_employees = {
emp.badge_id: emp emp.badge_id: emp
for emp in Employee.objects.entire() for emp in Employee.objects.entire()
@@ -495,17 +714,25 @@ def bulk_create_work_info_import(success_lists):
comp.company: comp comp.company: comp
for comp in Company.objects.filter(company__in=companies).only("company") for comp in Company.objects.filter(company__in=companies).only("company")
} }
reporting_manager_dict = optimize_reporting_manager_lookup(success_lists) reporting_manager_dict = optimize_reporting_manager_lookup()
for work_info in success_lists: for work_info in success_lists:
email = work_info["Email"] email = work_info["Email"]
badge_id = work_info["Badge id"] badge_id = work_info["Badge ID"]
department_obj = existing_departments.get(work_info.get("Department")) department_obj = existing_departments.get(work_info.get("Department"))
key = (
job_position_key = (
existing_departments.get(work_info.get("Department")), existing_departments.get(work_info.get("Department")),
work_info.get("Job Position"), work_info.get("Job Position"),
) )
job_position_obj = existing_job_positions.get(key) job_position_obj = existing_job_positions.get(job_position_key)
job_role_obj = existing_job_roles.get(work_info.get("Job Role"))
job_role_key = (
job_position_obj,
work_info.get("Job Role"),
)
job_role_obj = existing_job_roles.get(job_role_key)
work_type_obj = existing_work_types.get(work_info.get("Work Type")) work_type_obj = existing_work_types.get(work_info.get("Work Type"))
employee_type_obj = existing_employee_types.get(work_info.get("Employee Type")) employee_type_obj = existing_employee_types.get(work_info.get("Employee Type"))
shift_obj = existing_shifts.get(work_info.get("Shift")) shift_obj = existing_shifts.get(work_info.get("Shift"))
@@ -520,8 +747,8 @@ def bulk_create_work_info_import(success_lists):
# Parsing dates and salary # Parsing dates and salary
date_joining = ( date_joining = (
work_info["Date joining"] work_info["Date Joining"]
if not pd.isnull(work_info["Date joining"]) if not pd.isnull(work_info["Date Joining"])
else datetime.today() else datetime.today()
) )

View File

@@ -492,6 +492,7 @@ $("#deleteEmployees").click(function (e) {
}).then(function (result) { }).then(function (result) {
if (result.isConfirmed) { if (result.isConfirmed) {
e.preventDefault(); e.preventDefault();
$("#view-container").html(`<div class="animated-background"></div>`);
ids = []; ids = [];
ids.push($("#selectedInstances").attr("data-ids")); ids.push($("#selectedInstances").attr("data-ids"));

View File

@@ -1,321 +1,179 @@
var downloadMessages = { var downloadMessages = {
ar: "هل ترغب في تنزيل القالب؟", ar: "هل ترغب في تنزيل القالب؟",
de: "Möchten Sie die Vorlage herunterladen?", de: "Möchten Sie die Vorlage herunterladen?",
es: "¿Quieres descargar la plantilla?", es: "¿Quieres descargar la plantilla?",
en: "Do you want to download the template?", en: "Do you want to download the template?",
fr: "Voulez-vous télécharger le modèle ?", fr: "Voulez-vous télécharger le modèle ?",
}; };
var importSuccess = { var importSuccess = {
ar: "نجح الاستيراد", // Arabic ar: "نجح الاستيراد", // Arabic
de: "Import erfolgreich", // German de: "Import erfolgreich", // German
es: "Importado con éxito", // Spanish es: "Importado con éxito", // Spanish
en: "Imported Successfully!", // English en: "Imported Successfully!", // English
fr: "Importation réussie", // French fr: "Importation réussie", // French
}; };
var uploadSuccess = { var uploadSuccess = {
ar: "تحميل كامل", // Arabic ar: "تحميل كامل", // Arabic
de: "Upload abgeschlossen", // German de: "Upload abgeschlossen", // German
es: "Carga completa", // Spanish es: "Carga completa", // Spanish
en: "Upload Complete!", // English en: "Upload Complete!", // English
fr: "Téléchargement terminé", // French fr: "Téléchargement terminé", // French
}; };
var uploadingMessage = { var uploadingMessage = {
ar: "جارٍ الرفع", ar: "جارٍ الرفع",
de: "Hochladen...", de: "Hochladen...",
es: "Subiendo...", es: "Subiendo...",
en: "Uploading...", en: "Uploading...",
fr: "Téléchargement en cours...", fr: "Téléchargement en cours...",
}; };
var validationMessage = { var validationMessage = {
ar: "يرجى تحميل ملف بامتداد .xlsx فقط.", ar: "يرجى تحميل ملف بامتداد .xlsx فقط.",
de: "Bitte laden Sie nur eine Datei mit der Erweiterung .xlsx hoch.", de: "Bitte laden Sie nur eine Datei mit der Erweiterung .xlsx hoch.",
es: "Por favor, suba un archivo con la extensión .xlsx solamente.", es: "Por favor, suba un archivo con la extensión .xlsx solamente.",
en: "Please upload a file with the .xlsx extension only.", en: "Please upload a file with the .xlsx extension only.",
fr: "Veuillez télécharger uniquement un fichier avec l'extension .xlsx.", fr: "Veuillez télécharger uniquement un fichier avec l'extension .xlsx.",
}; };
function getCookie(name) { function getCookie(name) {
let cookieValue = null; let cookieValue = null;
if (document.cookie && document.cookie !== "") { if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";"); const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) { for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim(); const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want? // Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === name + "=") { if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break; break;
} }
}
} }
} return cookieValue;
return cookieValue;
} }
function getCurrentLanguageCode(callback) { function getCurrentLanguageCode(callback) {
var languageCode = $("#main-section-data").attr("data-lang"); var languageCode = $("#main-section-data").attr("data-lang");
var allowedLanguageCodes = ["ar", "de", "es", "en", "fr"]; var allowedLanguageCodes = ["ar", "de", "es", "en", "fr"];
if (allowedLanguageCodes.includes(languageCode)) { if (allowedLanguageCodes.includes(languageCode)) {
callback(languageCode); callback(languageCode);
} else { } else {
$.ajax({ $.ajax({
type: "GET", type: "GET",
url: "/employee/get-language-code/", url: "/employee/get-language-code/",
success: function (response) { success: function (response) {
var ajaxLanguageCode = response.language_code; var ajaxLanguageCode = response.language_code;
$("#main-section-data").attr("data-lang", ajaxLanguageCode); $("#main-section-data").attr("data-lang", ajaxLanguageCode);
callback( callback(
allowedLanguageCodes.includes(ajaxLanguageCode) allowedLanguageCodes.includes(ajaxLanguageCode)
? ajaxLanguageCode ? ajaxLanguageCode
: "en" : "en"
); );
}, },
error: function () { error: function () {
callback("en"); callback("en");
}, },
});
}
}
// Get the form element
var form = document.getElementById("workInfoImportForm");
// Add an event listener to the form submission
form.addEventListener("submit", function (event) {
// Prevent the default form submission
event.preventDefault();
// Create a new form data object
$(".oh-dropdown__import-form").css("display", "none");
$("#uploading").css("display", "block");
var formData = new FormData();
// Append the file to the form data object
var fileInput = document.querySelector("#workInfoImportFile");
formData.append("file", fileInput.files[0]);
$.ajax({
type: "POST",
url: "/employee/work-info-import",
dataType: "binary",
data: formData,
processData: false,
contentType: false,
headers: {
"X-CSRFToken": getCookie("csrftoken"),
},
xhrFields: {
responseType: "blob",
},
success: function (response, textStatus, xhr) {
var errorCount = xhr.getResponseHeader('X-Error-Count');
if (typeof response === 'object' && response.type == 'application/json') {
var reader = new FileReader();
reader.onload = function() {
var json = JSON.parse(reader.result);
if(json.success_count > 0) {
Swal.fire({
text: `${json.success_count} Employees Imported Successfully`,
icon: "success",
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
}).then(function() {
window.location.reload();
});
}
}
reader.readAsText(response);
return;
}
if (!$(".file-xlsx-validation").length) {
swal.fire({
text: `You have ${errorCount} errors. Do you want to download the error list?`,
icon: "error",
showCancelButton: true,
showDenyButton: true,
confirmButtonText: "Download error list & Skip Import",
denyButtonText: "Downlod error list & Continue Import",
cancelButtonText: "Cancel",
confirmButtonColor: "#d33",
denyButtonColor: "#008000",
customClass: {
container: 'custom-swal-container'
}
})
.then((result) => {
if (result.isConfirmed) {
const file = new Blob([response], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
const url = URL.createObjectURL(file);
const link = document.createElement("a");
link.href = url;
link.download = "ImportError.xlsx";
document.body.appendChild(link);
link.click();
window.location.reload();
}
else if (result.isDenied) {
formData.append("create_work_info", true);
$.ajax({
type: "POST",
url: "/employee/work-info-import",
dataType: "binary",
data: formData,
processData: false,
contentType: false,
headers: {
"X-CSRFToken": getCookie("csrftoken"),
},
xhrFields: {
responseType: "blob",
},
success: function (response, textStatus, xhr) {
Swal.fire({
text: `Employees Imported Successfully`,
icon: "success",
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
}).then(function() {
const file = new Blob([response], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
const url = URL.createObjectURL(file);
const link = document.createElement("a");
link.href = url;
link.download = "ImportError.xlsx";
document.body.appendChild(link);
link.click();
window.location.reload();
});
return;
}
})
}
else {
$(".oh-dropdown__import-form").css("display", "block");
$("#uploading").css("display", "none");
}
}); });
} }
}
},
error: function (xhr, textStatus, errorThrown) {
console.error("Error downloading file:", errorThrown);
},
});
});
function template_download(e) { function template_download(e) {
e.preventDefault(); e.preventDefault();
var languageCode = null; var languageCode = null;
getCurrentLanguageCode(function (code) { getCurrentLanguageCode(function (code) {
languageCode = code; languageCode = code;
var confirmMessage = downloadMessages[languageCode]; var confirmMessage = downloadMessages[languageCode];
Swal.fire({ Swal.fire({
text: confirmMessage, text: confirmMessage,
icon: "question", icon: "question",
showCancelButton: true, showCancelButton: true,
confirmButtonColor: "#008000", confirmButtonColor: "#008000",
cancelButtonColor: "#d33", cancelButtonColor: "#d33",
confirmButtonText: "Confirm", confirmButtonText: "Confirm",
}).then(function (result) { }).then(function (result) {
if (result.isConfirmed) { if (result.isConfirmed) {
$("#loading").show(); $("#loading").show();
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open("GET", "/employee/work-info-import", true); xhr.open("GET", "/employee/work-info-import-file", true);
xhr.responseType = "arraybuffer"; xhr.responseType = "arraybuffer";
xhr.upload.onprogress = function (e) { xhr.upload.onprogress = function (e) {
if (e.lengthComputable) { if (e.lengthComputable) {
var percent = (e.loaded / e.total) * 100; var percent = (e.loaded / e.total) * 100;
$(".progress-bar") $(".progress-bar")
.width(percent + "%") .width(percent + "%")
.attr("aria-valuenow", percent); .attr("aria-valuenow", percent);
$("#progress-text").text( $("#progress-text").text(
"Uploading... " + percent.toFixed(2) + "%" "Uploading... " + percent.toFixed(2) + "%"
); );
} }
}; };
xhr.onload = function (e) { xhr.onload = function (e) {
if (this.status == 200) { if (this.status == 200) {
const file = new Blob([this.response], { const file = new Blob([this.response], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}); });
const url = URL.createObjectURL(file); const url = URL.createObjectURL(file);
const link = document.createElement("a"); const link = document.createElement("a");
link.href = url; link.href = url;
link.download = "work_info_template.xlsx"; link.download = "work_info_template.xlsx";
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
} }
}; };
xhr.onerror = function (e) { xhr.onerror = function (e) {
console.error("Error downloading file:", e); console.error("Error downloading file:", e);
}; };
xhr.send(); xhr.send();
} }
});
}); });
});
} }
$("#work-info-import-download").click(function (e) {
template_download(e); // Pass the event to the function
});
$("#work-info-import").click(function (e) {
template_download(e); // Pass the event to the function
});
$(document).ajaxStart(function () { $(document).ajaxStart(function () {
$("#loading").show(); $("#loading").show();
}); });
$(document).ajaxStop(function () { $(document).ajaxStop(function () {
$("#loading").hide(); $("#loading").hide();
}); });
function simulateProgress() { function simulateProgress() {
getCurrentLanguageCode(function (code) { getCurrentLanguageCode(function (code) {
let progressBar = document.querySelector(".progress-bar"); let progressBar = document.querySelector(".progress-bar");
let progressText = document.getElementById("progress-text"); let progressText = document.getElementById("progress-text");
let width = 0; let width = 0;
let interval = setInterval(function () { let interval = setInterval(function () {
if (width >= 100) { if (width >= 100) {
clearInterval(interval); clearInterval(interval);
progressText.innerText = uploadMessage; progressText.innerText = uploadMessage;
setTimeout(function () { setTimeout(function () {
document.getElementById("loading").style.display = "none"; document.getElementById("loading").style.display = "none";
}, 3000); }, 3000);
Swal.fire({ Swal.fire({
text: importMessage, text: importMessage,
icon: "success", icon: "success",
showConfirmButton: false, showConfirmButton: false,
timer: 2000, timer: 2000,
timerProgressBar: true, timerProgressBar: true,
}); });
setTimeout(function () { setTimeout(function () {
$("#workInfoImport").removeClass("oh-modal--show"); $("#workInfoImport").removeClass("oh-modal--show");
location.reload(true); location.reload(true);
}, 2000); }, 2000);
} else { } else {
width++; width++;
progressBar.style.width = width + "%"; progressBar.style.width = width + "%";
progressBar.setAttribute("aria-valuenow", width); progressBar.setAttribute("aria-valuenow", width);
progressText.innerText = uploadingMessage[languageCode] + width + "%"; progressText.innerText = uploadingMessage[languageCode] + width + "%";
} }
}, 20); }, 20);
}); });
} }

View File

@@ -0,0 +1,49 @@
{% load i18n %}
<div class="oh-modal__dialog-header">
<h2 class="oh-modal__dialog-title">
{% trans "Import Employee" %}
</h2>
<button class="oh-modal__close" aria-label="Close">
<ion-icon name="close-outline"></ion-icon>
</button>
<div class="oh-modal__dialog-body p-0 pb-4">
<form hx-post="{% url 'work-info-import' %}" hx-target="#objectCreateModalTarget" hx-encoding="multipart/form-data" class="oh-profile-section">
{% csrf_token %}
<div class="oh-modal__dialog-body mr-5" id="uploading" style="display: none">
<div class="loader-container">
<div class="loader"></div>
<div class="loader-text">{% trans "Uploading..." %}</div>
</div>
</div>
<div id="error-container" style="color: red"></div>
<div class="modal-body">
<div class="oh-dropdown__import-form">
{% if error_message %}
<ul class="errorlist">
<li>{{error_message}}</li>
</ul>
{% endif %}
<label class="oh-dropdown__import-label">
<ion-icon name="cloud-upload" class="oh-dropdown__import-form-icon md hydrated" role="img"
aria-label="cloud upload"></ion-icon>
<span class="oh-dropdown__import-form-title">{% trans "Upload a File" %}</span>
<span class="oh-dropdown__import-form-text">{% trans "Drag and drop files here" %}</span>
</label>
<input type="file" name="file"/ required>
<div class="d-inline float-end">
<a href="#" style="text-decoration:none; display: inline-block;" class="oh-dropdown__link" hx-on:click="template_download(event)">
<ion-icon name="cloud-download-outline" style="font-size:20px; vertical-align: middle;" role="img" class="md hydrated"></ion-icon>
<span>{% trans "Download Template" %}</span>
</a>
</div>
</div>
</div>
<div class="modal-footer d-flex flex-row-reverse">
<input type="submit" class="oh-btn oh-btn--small oh-btn--secondary w-100 mt-3"
value="{% trans 'Upload' %}" />
</div>
</form>
</div>
</div>

View File

@@ -1,578 +1,389 @@
{% load static %} {% load i18n %} {% load static %} {% load i18n %}
<style> <style>
#progress { #progress {
width: 300px; width: 300px;
height: 30px; height: 30px;
border: 1px solid #ccc; border: 1px solid #ccc;
background-color: #f0f0f0; background-color: #f0f0f0;
margin-bottom: 10px; margin-bottom: 10px;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
} }
.progress-bar { .progress-bar {
height: 100%; height: 100%;
background-color: #4caf50; background-color: #4caf50;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
transition: width 0.2s ease-in-out; transition: width 0.2s ease-in-out;
} }
#progress-text { #progress-text {
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
} }
.custom-swal-container .swal2-styled {
display:block; .custom-swal-container .swal2-styled {
width:70%; display: block;
} width: 70%;
}
</style> </style>
{% if perms.employee.add_employee %}
<div
class="oh-modal"
id="workInfoImport"
role="dialog"
aria-labelledby="workInfoImport"
aria-hidden="true"
>
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<h2 class="oh-modal__dialog-title" id="workInfoImportLavel">
{% trans "Import Employee" %}
</h2>
<button class="oh-modal__close" aria-label="Close">
<ion-icon name="close-outline"></ion-icon>
</button>
<div class="oh-modal__dialog-body p-0 pt-2" id="workInfoImportModalBody">
<form
action="#"
id="workInfoImportForm"
enctype="multipart/form-data"
>
<div
class="oh-modal__dialog-body mr-5"
id="uploading"
style="display: none"
>
<div class="loader-container">
<div class="loader"></div>
<div class="loader-text">{% trans "Uploading..." %}</div>
</div>
</div>
<div id="error-container" style="color: red"></div>
<div class="modal-body">
{% csrf_token %}
<div class="oh-dropdown__import-form">
<label class="oh-dropdown__import-label" for="workInfoImportFile">
<ion-icon
name="cloud-upload"
class="oh-dropdown__import-form-icon md hydrated"
role="img"
aria-label="cloud upload"
></ion-icon>
<span class="oh-dropdown__import-form-title"
>{% trans "Upload a File" %}</span
>
<span class="oh-dropdown__import-form-text"
>{% trans "Drag and drop files here" %}</span
>
</label>
<input type="file" name="file" id="workInfoImportFile" />
<div class="d-inline float-end">
<a
href="#"
style="text-decoration:none; display: inline-block;"
class="oh-dropdown__link"
id="work-info-import-download"
data-toggle="oh-modal-toggle"
data-target="#workInfoImport"
>
<ion-icon name="cloud-download-outline" style="font-size:20px; vertical-align: middle;"></ion-icon>
<span>{% trans "Download Template" %}</span>
</a>
</div>
</div>
</div>
<div class="modal-footer d-flex flex-row-reverse">
<input
onclick="
validateFile($(this),'workInfoImportFile',true);
"
type="submit"
class="oh-btn oh-btn--small oh-btn--secondary w-100 mt-3"
value="{% trans 'Upload' %}"
/>
</div>
</form>
</div>
</div>
</div>
</div>
<div id="employeeExport" class="oh-modal" role="dialog" aria-labelledby="employeeExport" aria-hidden="true"> <div id="employeeExport" class="oh-modal" role="dialog" aria-labelledby="employeeExport" aria-hidden="true">
<div id="employeeExportTarget" class="oh-modal__dialog" style="max-width: 750px" ></div> <div id="employeeExportTarget" class="oh-modal__dialog" style="max-width: 750px"></div>
</div> </div>
{% endif %}
{% if perms.employee.change_employee %} {% if perms.employee.change_employee %}
<div <div class="oh-modal" id="bulkUpdateModal" role="dialog" aria-labelledby="bulkUpdateModal" aria-hidden="true">
class="oh-modal" <div class="oh-modal__dialog" style="max-width: 750px">
id="bulkUpdateModal" <div class="oh-modal__dialog-header">
role="dialog" <h2 class="oh-modal__dialog-title" id="bulkUpdateModalLavel">
aria-labelledby="bulkUpdateModal" {% trans "Bulk Update Employees" %}
aria-hidden="true" </h2>
> <button class="oh-modal__close" aria-label="Close">
<div class="oh-modal__dialog" style="max-width: 750px"> <ion-icon name="close-outline"></ion-icon>
<div class="oh-modal__dialog-header"> </button>
<h2 class="oh-modal__dialog-title" id="bulkUpdateModalLavel"> <div class="oh-modal__dialog-body p-0 pt-2 pb-4" id="bulkUpdateModalBody">
{% trans "Bulk Update Employees" %} <form action="{%url 'employee-bulk-update' %}" method="post"
</h2> onsubmit="event.stopPropagation();$(this).parents().find('.oh-modal--show').last().toggleClass('oh-modal--show');"
<button class="oh-modal__close" aria-label="Close"> id="bulkUpdateModalForm" class="oh-profile-section">
<ion-icon name="close-outline"></ion-icon> {% csrf_token %} {{update_fields_form.update_fields.label}}
</button> {{update_fields_form.update_fields}}
<div class="oh-modal__dialog-body p-0 pt-2 pb-4" id="bulkUpdateModalBody"> {{update_fields_form.bulk_employee_ids}}
<form <div class="oh-modal__dialog-footer p-0 pt-4">
action="{%url 'employee-bulk-update' %}" <button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
method="post" {% trans "Update" %}
onsubmit="event.stopPropagation();$(this).parents().find('.oh-modal--show').last().toggleClass('oh-modal--show');" </button>
id="bulkUpdateModalForm" </div>
class="oh-profile-section" </form>
> </div>
{% csrf_token %} {{update_fields_form.update_fields.label}} </div>
{{update_fields_form.update_fields}} </div>
{{update_fields_form.bulk_employee_ids}}
<div class="oh-modal__dialog-footer p-0 pt-4">
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
{% trans "Update" %}
</button>
</div>
</form>
</div>
</div> </div>
</div>
</div>
{% endif %} {% endif %}
<section class="oh-wrapper oh-main__topbar pb-3" x-data="{searchShow: false}"> <section class="oh-wrapper oh-main__topbar pb-3" x-data="{searchShow: false}">
<div class="oh-main__titlebar oh-main__titlebar--left"> <div class="oh-main__titlebar oh-main__titlebar--left">
<a <a href="{% url 'employee-view' %}" class="oh-main__titlebar-title fw-bold mb-0 text-dark"
href="{% url 'employee-view' %}" hx-target="#view-container" style="cursor: pointer">{% trans "Employees" %}</a>
class="oh-main__titlebar-title fw-bold mb-0 text-dark" <a class="oh-main__titlebar-search-toggle" role="button" aria-label="Toggle Search"
hx-target="#view-container" @click="searchShow = !searchShow">
style="cursor: pointer" <ion-icon name="search-outline" class="oh-main__titlebar-serach-icon"></ion-icon>
>{% trans "Employees" %}</a </a>
> </div>
<a
class="oh-main__titlebar-search-toggle"
role="button"
aria-label="Toggle Search"
@click="searchShow = !searchShow"
>
<ion-icon
name="search-outline"
class="oh-main__titlebar-serach-icon"
></ion-icon>
</a>
</div>
<div class="oh-main__titlebar oh-main__titlebar--right"> <div class="oh-main__titlebar oh-main__titlebar--right">
<form <form hx-get='{% url "employee-filter-view" %}' id="filterForm" hx-target="#view-container" class="d-flex"
hx-get='{% url "employee-filter-view" %}' onsubmit="event.preventDefault()">
id="filterForm" {% if emp %}
hx-target="#view-container" <div class="oh-input-group oh-input__search-group"
class="d-flex" :class="searchShow ? 'oh-input__search-group--show' : ''">
onsubmit="event.preventDefault()" <ion-icon name="search-outline" class="oh-input-group__icon oh-input-group__icon--left"></ion-icon>
> <input type="text" placeholder="{% trans 'Search' %}" name="search" id="employee-search"
{% if emp %} class="oh-input oh-input__icon" aria-label="Search Input" onkeyup="$('.filterButton')[0].click();if(this.value) {
<div $('.search_text').html(this.value)
class="oh-input-group oh-input__search-group" $(this).parent().find('#dropdown').show()
:class="searchShow ? 'oh-input__search-group--show' : ''" }else{
> $(this).parent().find('#dropdown').hide()
<ion-icon }" onfocus="
name="search-outline" if (this.value) {
class="oh-input-group__icon oh-input-group__icon--left" $(this).parent().find('#dropdown').show()
></ion-icon> }" onfocusout="
<input setTimeout(function() {
type="text" $('#dropdown').hide()
placeholder="{% trans 'Search' %}" }, 300);
name="search" "
id="employee-search" />
class="oh-input oh-input__icon" <input type="text" hidden name="search_field">
aria-label="Search Input" <div class="custom-dropdown" id="dropdown">
onkeyup="$('.filterButton')[0].click();if(this.value) { <ul class="search_content">
$('.search_text').html(this.value) <li>
$(this).parent().find('#dropdown').show() <a href="#"
}else{ onclick="$('[name=search_field]').val('reporting_manager'); $('.filterButton')[0].click()">
$(this).parent().find('#dropdown').hide() {% trans "Search" %} <b>{% trans "Reporting Manager" %}</b> {% trans "for:" %}
}" <b class="search_text"></b>
onfocus=" </a>
if (this.value) { </li>
$(this).parent().find('#dropdown').show() <li>
}" <a href="#"
onfocusout=" onclick="$('[name=search_field]').val('department'); $('.filterButton')[0].click()">
setTimeout(function() { {% trans "Search" %} <b>{% trans "Department" %}</b> {% trans "for:" %}
$('#dropdown').hide() <b class="search_text"></b>
}, 300); </a>
" </li>
/> <li>
<input type="text" hidden name="search_field"> <a href="#"
<div class="custom-dropdown" id="dropdown"> onclick="$('[name=search_field]').val('job_position'); $('.filterButton')[0].click()">
<ul class="search_content"> {% trans "Search" %} <b>{% trans "Job Position" %}</b> {% trans "for:" %}
<li> <b class="search_text"></b>
<a href="#" onclick="$('[name=search_field]').val('reporting_manager'); $('.filterButton')[0].click()"> </a>
{% trans "Search" %} <b>{% trans "Reporting Manager" %}</b> {% trans "for:" %} </li>
<b class="search_text"></b> <li>
</a> <a href="#"
</li> onclick="$('[name=search_field]').val('job_role'); $('.filterButton')[0].click()">
<li> {% trans "Search" %} <b>{% trans "Job Role" %}</b> {% trans "for:" %}
<a href="#" onclick="$('[name=search_field]').val('department'); $('.filterButton')[0].click()"> <b class="search_text"></b>
{% trans "Search" %} <b>{% trans "Department" %}</b> {% trans "for:" %} </a>
<b class="search_text"></b> </li>
</a> <li>
</li> <a href="#" onclick="$('[name=search_field]').val('shift'); $('.filterButton')[0].click()">
<li> {% trans "Search" %} <b>{% trans "Shift" %}</b> {% trans "for:" %}
<a href="#" onclick="$('[name=search_field]').val('job_position'); $('.filterButton')[0].click()"> <b class="search_text"></b>
{% trans "Search" %} <b>{% trans "Job Position" %}</b> {% trans "for:" %} </a>
<b class="search_text"></b> </li>
</a> <li>
</li> <a href="#"
<li> onclick="$('[name=search_field]').val('work_type'); $('.filterButton')[0].click()">
<a href="#" onclick="$('[name=search_field]').val('job_role'); $('.filterButton')[0].click()"> {% trans "Search" %} <b>{% trans "Work Type" %}</b> {% trans "for:" %}
{% trans "Search" %} <b>{% trans "Job Role" %}</b> {% trans "for:" %} <b class="search_text"></b>
<b class="search_text"></b> </a>
</a> </li>
</li> <li>
<li> <a href="#"
<a href="#" onclick="$('[name=search_field]').val('shift'); $('.filterButton')[0].click()"> onclick="$('[name=search_field]').val('company'); $('.filterButton')[0].click()">
{% trans "Search" %} <b>{% trans "Shift" %}</b> {% trans "for:" %} {% trans "Search" %} <b>{% trans "Company" %}</b> {% trans "for:" %}
<b class="search_text"></b> <b class="search_text"></b>
</a> </a>
</li> </li>
<li>
<a href="#" onclick="$('[name=search_field]').val('work_type'); $('.filterButton')[0].click()">
{% trans "Search" %} <b>{% trans "Work Type" %}</b> {% trans "for:" %}
<b class="search_text"></b>
</a>
</li>
<li>
<a href="#" onclick="$('[name=search_field]').val('company'); $('.filterButton')[0].click()">
{% trans "Search" %} <b>{% trans "Company" %}</b> {% trans "for:" %}
<b class="search_text"></b>
</a>
</li>
</ul> </ul>
</div> </div>
</div>
{% endif %}
<div class="oh-main__titlebar-button-container">
{% if emp %}
<input
type="hidden"
name="view"
value="{{request.GET.view}}"
id="employeeViewType"
/>
<ul class="oh-view-types ml-2" style="margin-bottom: 0">
<li class="oh-view-type employee-view-type" data-view="list">
<a
id="list"
onclick="$('#employeeViewType').val('list');setTimeout(() => {
$('.filterButton')[0].click();
}, 10);"
class="oh-btn oh-btn--view {% if request.GET.view == 'list' %} oh-btn--view-active {% endif %}"
title='{% trans "List" %}'
><ion-icon name="list-outline"></ion-icon
></a>
</li>
<li class="oh-view-type employee-view-type" data-view="card">
<a
id="card"
onclick="$('#employeeViewType').val('card');setTimeout(() => {
$('.filterButton')[0].click();
}, 10);"
class="oh-btn oh-btn--view {% if request.GET.view != 'list' %} oh-btn--view-active {% endif %}"
title='{% trans "Card" %}'
><ion-icon name="grid-outline"></ion-icon
></a>
</li>
</ul>
{% endif %} {% if emp %}
<div class="oh-dropdown" x-data="{open: false}">
<button
class="oh-btn ml-2"
@click="open = !open"
onclick="event.preventDefault()"
>
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
<div id="filterCount"></div>
</button>
<div
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
x-show="open"
@click.outside="open = false"
style="display: none"
>
{% include 'employee_filters.html' %}
<div class="oh-dropdown__filter-footer">
<button
class="oh-btn oh-btn--secondary oh-btn--small w-100 filterButton"
id="#employeeFilter"
onclick="employeeFilter(this)"
>
{% trans "Filter" %}
</button>
</div>
</div>
</div>
<div class="oh-dropdown" x-data="{open: false}">
<button
class="oh-btn ml-2"
@click="open = !open"
onclick="event.preventDefault()"
>
<ion-icon name="library-outline" class="mr-1"></ion-icon>
{% trans "Group By" %}
<div id="filterCount"></div>
</button>
<div
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
x-show="open"
@click.outside="open = false"
style="display: none"
>
<div class="oh-accordion">
<label for="id_field">{% trans "Group By" %}</label>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group">
<label class="oh-label" for="id_field"
>{% trans "Field" %}</label
>
</div>
</div> </div>
<div class="col-sm-12 col-md-12 col-lg-6"> {% endif %}
<div class="oh-input-group">
<select <div class="oh-main__titlebar-button-container">
class="oh-select mt-1 w-100" {% if emp %}
id="id_field" <input type="hidden" name="view" value="{{request.GET.view}}" id="employeeViewType" />
name="field" <ul class="oh-view-types ml-2" style="margin-bottom: 0">
class="select2-selection select2-selection--single" <li class="oh-view-type employee-view-type" data-view="list">
> <a id="list" onclick="$('#employeeViewType').val('list');setTimeout(() => { $('.filterButton')[0].click(); }, 10);"
{% for field in gp_fields %} class="oh-btn oh-btn--view {% if request.GET.view == 'list' %} oh-btn--view-active {% endif %}"
<option value="{{ field.0 }}">{% trans field.1 %}</option> title='{% trans "List" %}'>
{% endfor %} <ion-icon name="list-outline"></ion-icon>
</select> </a>
</div> </li>
</div> <li class="oh-view-type employee-view-type" data-view="card">
</div> <a id="card" onclick="$('#employeeViewType').val('card');setTimeout(() => { $('.filterButton')[0].click(); }, 10);"
</div> class="oh-btn oh-btn--view {% if request.GET.view != 'list' %} oh-btn--view-active {% endif %}"
</div> title='{% trans "Card" %}'>
</div> <ion-icon name="grid-outline"></ion-icon>
{% endif %} </a>
{% if perms.employee.change_employee or perms.employee.add_employee or perms.employee.delete_employee %} </li>
<div class="oh-btn-group ml-2" onclick="event.preventDefault();"> </ul>
<div class="oh-dropdown" x-data="{open: false}">
<button
class="oh-btn oh-btn--dropdown"
@click="open = !open"
@click.outside="open = false"
>
{% trans "Actions" %}
</button>
<div
class="oh-dropdown__menu oh-dropdown__menu--right"
x-show="open"
style="display: none"
>
<ul class="oh-dropdown__items">
{% if perms.employee.add_employee %}
<li class="oh-dropdown__item">
<a
href="#"
class="oh-dropdown__link"
id="work-info-import"
data-toggle="oh-modal-toggle"
data-target="#workInfoImport"
>
{% trans "Import" %}
</a>
</li>
{% endif %} {% endif %}
{% if emp %} {% if emp %}
{% if perms.employee.add_employee %} <div class="oh-dropdown" x-data="{open: false}">
<li class="oh-dropdown__item"> <button class="oh-btn ml-2" @click="open = !open" onclick="event.preventDefault()">
<a <ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
href="#" <div id="filterCount"></div>
class="oh-dropdown__link" </button>
id="employee-info-export" <div class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4" x-show="open"
data-toggle="oh-modal-toggle" @click.outside="open = false" style="display: none">
data-target="#employeeExport" {% include 'employee_filters.html' %}
hx-get="{% url 'work-info-export' %}" <div class="oh-dropdown__filter-footer">
hx-target="#employeeExportTarget" <button class="oh-btn oh-btn--secondary oh-btn--small w-100 filterButton"
> id="#employeeFilter" onclick="employeeFilter(this)">
{% trans "Export" %} {% trans "Filter" %}
</a> </button>
</li> </div>
{% endif %} </div>
{% if perms.employee.change_employee %} </div>
<li class="oh-dropdown__item"> <div class="oh-dropdown" x-data="{open: false}">
<a href="#" class="oh-dropdown__link" id="archiveEmployees"> <button class="oh-btn ml-2" @click="open = !open" onclick="event.preventDefault()">
{% trans "Archive" %} <ion-icon name="library-outline" class="mr-1"></ion-icon>
</a> {% trans "Group By" %}
</li> <div id="filterCount"></div>
<li class="oh-dropdown__item"> </button>
<a href="#" class="oh-dropdown__link" id="unArchiveEmployees"> <div class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4" x-show="open"
{% trans "Un-Archive" %} @click.outside="open = false" style="display: none">
</a> <div class="oh-accordion">
</li> <label for="id_field">{% trans "Group By" %}</label>
<li class="oh-dropdown__item"> <div class="row">
<a <div class="col-sm-12 col-md-12 col-lg-6">
hx-get="{% url 'employee-bulk-mail' %}" <div class="oh-input-group">
hx-target="#mail-content" <label class="oh-label" for="id_field">{% trans "Field" %}</label>
href="#" </div>
class="oh-dropdown__link " </div>
data-toggle="oh-modal-toggle" <div class="col-sm-12 col-md-12 col-lg-6">
data-target="#sendMailModal" <div class="oh-input-group">
> <select class="oh-select mt-1 w-100" id="id_field" name="field"
{% trans "Bulk Mail" %} class="select2-selection select2-selection--single">
</a> {% for field in gp_fields %}
</li> <option value="{{ field.0 }}">{% trans field.1 %}</option>
<li class="oh-dropdown__item"> {% endfor %}
<a </select>
href="#" </div>
class="oh-dropdown__link" </div>
id="employeeBulkUpdateId" </div>
> </div>
{% trans "Bulk Update" %} </div>
</a> </div>
</li> {% endif %}
{% endif %} {% if perms.employee.change_employee or perms.employee.add_employee or perms.employee.delete_employee %}
{% if perms.employee.delete_employee %} <div class="oh-btn-group ml-2" onclick="event.preventDefault();">
<li class="oh-dropdown__item"> <div class="oh-dropdown" x-data="{open: false}">
<a <button class="oh-btn oh-btn--dropdown" @click="open = !open" @click.outside="open = false">
href="#" {% trans "Actions" %}
class="oh-dropdown__link oh-dropdown__link--danger" </button>
data-action="delete" <div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open" style="display: none">
id="deleteEmployees" <ul class="oh-dropdown__items">
> {% if perms.employee.add_employee %}
{% trans "Delete" %} <li class="oh-dropdown__item">
</a> <a href="#" class="oh-dropdown__link"
</li> data-toggle="oh-modal-toggle" data-target="#objectCreateModal"
{% endif %} hx-get="{% url 'work-info-import' %}" hx-target="#objectCreateModalTarget"
hx-on-htmx-after-request="setTimeout(() => {template_download(event);},100);">
{% trans "Import" %}
</a>
</li>
{% endif %}
{% if emp %}
{% if perms.employee.add_employee %}
<li class="oh-dropdown__item">
<a href="#" class="oh-dropdown__link" id="employee-info-export"
data-toggle="oh-modal-toggle" data-target="#employeeExport"
hx-get="{% url 'work-info-export' %}" hx-target="#employeeExportTarget">
{% trans "Export" %}
</a>
</li>
{% endif %}
{% if perms.employee.change_employee %}
<li class="oh-dropdown__item">
<a href="#" class="oh-dropdown__link" id="archiveEmployees">
{% trans "Archive" %}
</a>
</li>
<li class="oh-dropdown__item">
<a href="#" class="oh-dropdown__link" id="unArchiveEmployees">
{% trans "Un-Archive" %}
</a>
</li>
<li class="oh-dropdown__item">
<a hx-get="{% url 'employee-bulk-mail' %}" hx-target="#mail-content" href="#"
class="oh-dropdown__link " data-toggle="oh-modal-toggle"
data-target="#sendMailModal">
{% trans "Bulk Mail" %}
</a>
</li>
<li class="oh-dropdown__item">
<a href="#" class="oh-dropdown__link" id="employeeBulkUpdateId">
{% trans "Bulk Update" %}
</a>
</li>
{% endif %}
{% if perms.employee.delete_employee %}
<li class="oh-dropdown__item">
<a href="#" class="oh-dropdown__link oh-dropdown__link--danger" data-action="delete"
id="deleteEmployees">
{% trans "Delete" %}
</a>
</li>
{% endif %}
{% endif %}
</ul>
</div>
</div>
</div>
{% endif %}
{% if perms.employee.add_employee %}
<div class="oh-btn-group ml-2">
<div class="oh-dropdown">
<a href='{% url "employee-view-new" %}' class="oh-btn oh-btn--secondary">
<ion-icon name="add-outline"></ion-icon>
{% trans "Create" %}
</a>
</div>
</div>
{% endif %} {% endif %}
</ul>
</div> </div>
</div> </form>
</div> </div>
{% endif %}
{% if perms.employee.add_employee %}
<div class="oh-btn-group ml-2">
<div class="oh-dropdown">
<a
href='{% url "employee-view-new" %}'
class="oh-btn oh-btn--secondary"
>
<ion-icon name="add-outline"></ion-icon>
{% trans "Create" %}
</a>
</div>
</div>
{% endif %}
</div>
</form>
</div>
</section> </section>
<script> <script>
function clearFilterFromTag(element) { function clearFilterFromTag(element) {
let field_id = element.attr("data-x-field"); let field_id = element.attr("data-x-field");
$(`[name=${field_id}]`).val(""); $(`[name=${field_id}]`).val("");
$(`[name=${field_id}]`).change(); $(`[name=${field_id}]`).change();
// Update all elements with the same ID to have null values // Update all elements with the same ID to have null values
let elementId = $(`[name=${field_id}]:last`).attr("id"); let elementId = $(`[name=${field_id}]:last`).attr("id");
let spanElement = $( let spanElement = $(
`.oh-dropdown__filter-body:first #select2-id_${field_id}-container, #select2-${elementId}-container` `.oh-dropdown__filter-body:first #select2-id_${field_id}-container, #select2-${elementId}-container`
); );
if (spanElement.length) { if (spanElement.length) {
spanElement.attr("title", "---------"); spanElement.attr("title", "---------");
spanElement.text("---------"); spanElement.text("---------");
}
$(".filterButton").click();
}
function clearAllFilter(element) {
$('[role="tooltip"]').remove();
let field_ids = $("[data-x-field]");
for (var i = 0; i < field_ids.length; i++) {
let item_id = field_ids[i].getAttribute("data-x-field");
$(`[name=${item_id}]`).val("");
$(`[name=${item_id}]`).change();
let elementId = $(`[name=${item_id}]:last`).attr("id");
let spanElement = $(
`.oh-dropdown__filter-body:first #select2-id_${item_id}-container, #select2-${elementId}-container`
);
if (spanElement.length) {
spanElement.attr("title", "---------");
spanElement.text("---------");
}
$(".filterButton").click();
localStorage.removeItem("savedFilters");
var url = window.location.href.split("?")[0];
window.history.replaceState({}, document.title, url);
}
}
function fieldLabel(value, field) {
if (field == "field") {
return $(`[value="${value}"]`).html();
}
return value;
}
$(document).ready(function () {
$("#employee-search").on("keyup", function () {
var searchFieldDiv = $("#searchFieldDiv");
var selectedField = searchFieldDiv.find(":selected");
if ($(this).val().trim() !== "") {
searchFieldDiv.show();
} else {
searchFieldDiv.hide();
selectedField.prop("selected", false);
}
$(".filterButton").eq(0).click();
});
$("#id_field").on("change", function () {
$(".filterButton")[0].click();
});
function filterFormSubmit(formId) {
var formData = $("#" + formId).serialize();
var count = 0;
formData.split("&").forEach(function (field) {
var parts = field.split("=");
var value = parts[1];
if (
value &&
value !== "unknown" &&
parts[0] != "field" &&
parts[0] != "view"
) {
count++;
} }
}); $(".filterButton").click();
$("#filterCount").empty();
if (count > 0) {
$("#filterCount").text(`(${count})`);
}
} }
function clearAllFilter(element) {
$('[role="tooltip"]').remove();
let field_ids = $("[data-x-field]");
for (var i = 0; i < field_ids.length; i++) {
let item_id = field_ids[i].getAttribute("data-x-field");
$("#filterForm").submit(function (e) { $(`[name=${item_id}]`).val("");
filterFormSubmit("filterForm"); $(`[name=${item_id}]`).change();
let elementId = $(`[name=${item_id}]:last`).attr("id");
let spanElement = $(
`.oh-dropdown__filter-body:first #select2-id_${item_id}-container, #select2-${elementId}-container`
);
if (spanElement.length) {
spanElement.attr("title", "---------");
spanElement.text("---------");
}
$(".filterButton").click();
localStorage.removeItem("savedFilters");
var url = window.location.href.split("?")[0];
window.history.replaceState({}, document.title, url);
}
}
function fieldLabel(value, field) {
if (field == "field") {
return $(`[value="${value}"]`).html();
}
return value;
}
$(document).ready(function () {
$("#employee-search").on("keyup", function () {
var searchFieldDiv = $("#searchFieldDiv");
var selectedField = searchFieldDiv.find(":selected");
if ($(this).val().trim() !== "") {
searchFieldDiv.show();
} else {
searchFieldDiv.hide();
selectedField.prop("selected", false);
}
$(".filterButton").eq(0).click();
});
$("#id_field").on("change", function () {
$(".filterButton")[0].click();
});
function filterFormSubmit(formId) {
var formData = $("#" + formId).serialize();
var count = 0;
formData.split("&").forEach(function (field) {
var parts = field.split("=");
var value = parts[1];
if (
value &&
value !== "unknown" &&
parts[0] != "field" &&
parts[0] != "view"
) {
count++;
}
});
$("#filterCount").empty();
if (count > 0) {
$("#filterCount").text(`(${count})`);
}
}
$("#filterForm").submit(function (e) {
filterFormSubmit("filterForm");
});
}); });
});
</script> </script>
<script src="{% static 'employee/importExport.js' %}"></script> <script src="{% static 'employee/importExport.js' %}"></script>
<script src="{% static 'employee/search.js' %}"></script> <script src="{% static 'employee/search.js' %}"></script>

View File

@@ -168,6 +168,11 @@ urlpatterns = [
path("employee-import", views.employee_import, name="employee-import"), path("employee-import", views.employee_import, name="employee-import"),
path("employee-export", views.employee_export, name="employee-export"), path("employee-export", views.employee_export, name="employee-export"),
path("work-info-import", views.work_info_import, name="work-info-import"), path("work-info-import", views.work_info_import, name="work-info-import"),
path(
"work-info-import-file",
views.work_info_import_file,
name="work-info-import-file",
),
path("work-info-export", views.work_info_export, name="work-info-export"), path("work-info-export", views.work_info_export, name="work-info-export"),
path("get-birthday", views.get_employees_birthday, name="get-birthday"), path("get-birthday", views.get_employees_birthday, name="get-birthday"),
path("dashboard", views.dashboard, name="dashboard"), path("dashboard", views.dashboard, name="dashboard"),

View File

@@ -16,7 +16,6 @@ import calendar
import json import json
import operator import operator
import os import os
import re
import threading import threading
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from urllib.parse import parse_qs from urllib.parse import parse_qs
@@ -28,12 +27,13 @@ from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import models, transaction from django.db import models
from django.db.models import F, ProtectedError from django.db.models import F, ProtectedError
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.forms import DateInput, Select from django.forms import DateInput, Select
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse, QueryDict from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as __ from django.utils.translation import gettext as __
@@ -57,17 +57,14 @@ from base.models import (
Company, Company,
Department, Department,
EmailLog, EmailLog,
EmployeeShift,
EmployeeType,
JobPosition, JobPosition,
JobRole, JobRole,
RotatingShiftAssign, RotatingShiftAssign,
RotatingWorkTypeAssign, RotatingWorkTypeAssign,
ShiftRequest, ShiftRequest,
WorkType,
WorkTypeRequest, WorkTypeRequest,
clear_messages,
) )
from base.views import generate_error_report
from employee.filters import DocumentRequestFilter, EmployeeFilter, EmployeeReGroup from employee.filters import DocumentRequestFilter, EmployeeFilter, EmployeeReGroup
from employee.forms import ( from employee.forms import (
BonusPointAddForm, BonusPointAddForm,
@@ -94,9 +91,11 @@ from employee.methods.methods import (
bulk_create_user_import, bulk_create_user_import,
bulk_create_work_info_import, bulk_create_work_info_import,
bulk_create_work_types, bulk_create_work_types,
convert_nan, error_data_template,
get_ordered_badge_ids, get_ordered_badge_ids,
process_employee_records,
set_initial_password, set_initial_password,
valid_import_file_headers,
) )
from employee.models import ( from employee.models import (
BonusPoint, BonusPoint,
@@ -191,7 +190,6 @@ def _check_reporting_manager(request, *args, **kwargs):
return request.user.employee_get.reporting_manager.exists() return request.user.employee_get.reporting_manager.exists()
# Create your views here.
@login_required @login_required
def get_language_code(request): def get_language_code(request):
""" """
@@ -1885,11 +1883,13 @@ def employee_bulk_delete(request):
""" """
This method is used to delete set of Employee instances This method is used to delete set of Employee instances
""" """
ids = request.POST["ids"] ids = json.loads(request.POST.get("ids", "[]"))
ids = json.loads(ids) if not ids:
for employee_id in ids: messages.error(request, _("No IDs provided."))
deleted_count = 0
employees = Employee.objects.filter(id__in=ids).select_related("employee_user_id")
for employee in employees:
try: try:
employee = Employee.objects.get(id=employee_id)
if apps.is_installed("payroll"): if apps.is_installed("payroll"):
if employee.contract_set.all().exists(): if employee.contract_set.all().exists():
contracts = employee.contract_set.all() contracts = employee.contract_set.all()
@@ -1898,16 +1898,19 @@ def employee_bulk_delete(request):
contract.delete() contract.delete()
user = employee.employee_user_id user = employee.employee_user_id
user.delete() user.delete()
messages.success( deleted_count += 1
request, _("%(employee)s deleted.") % {"employee": employee}
)
except Employee.DoesNotExist: except Employee.DoesNotExist:
messages.error(request, _("Employee not found.")) messages.error(request, _("Employee not found."))
except ProtectedError: except ProtectedError:
messages.error( messages.error(
request, _("You cannot delete %(employee)s.") % {"employee": employee} request, _("You cannot delete %(employee)s.") % {"employee": employee}
) )
if deleted_count > 0:
messages.success(
request,
_("%(deleted_count)s employees deleted.")
% {"deleted_count": deleted_count},
)
return JsonResponse({"message": "Success"}) return JsonResponse({"message": "Success"})
@@ -2419,19 +2422,19 @@ def convert_nan(field, dicts):
try: try:
float(field_value) float(field_value)
return None return None
except ValueError: except (ValueError, TypeError):
return field_value return field_value
@login_required @login_required
@permission_required("employee.add_employee") @permission_required("employee.add_employee")
def work_info_import(request): def work_info_import_file(request):
""" """
This method is used to import Employee instances and creates related objects This method is used to return the excel file of import Employee instances
""" """
data_frame = pd.DataFrame( data_frame = pd.DataFrame(
columns=[ columns=[
"Badge id", "Badge ID",
"First Name", "First Name",
"Last Name", "Last Name",
"Phone", "Phone",
@@ -2446,234 +2449,121 @@ def work_info_import(request):
"Reporting Manager", "Reporting Manager",
"Company", "Company",
"Location", "Location",
"Date joining", "Date Joining",
"Contract End Date", "Contract End Date",
"Basic Salary", "Basic Salary",
"Salary Hour", "Salary Hour",
] ]
) )
error_data = {
"Badge id": [],
"First Name": [],
"Last Name": [],
"Phone": [],
"Email": [],
"Gender": [],
"Department": [],
"Job Position": [],
"Job Role": [],
"Work Type": [],
"Shift": [],
"Employee Type": [],
"Reporting Manager": [],
"Company": [],
"Location": [],
"Date joining": [],
"Contract End Date": [],
"Basic Salary": [],
"Salary Hour": [],
"Email Error": [],
"First Name error": [],
"Name and Email Error": [],
"Phone error": [],
"Joining Date Error": [],
"Contract Error": [],
"Badge ID Error": [],
"Basic Salary Error": [],
"Salary Hour Error": [],
"User ID Error": [],
}
# Export the DataFrame to an Excel file
response = HttpResponse(content_type="application/ms-excel") response = HttpResponse(content_type="application/ms-excel")
response["Content-Disposition"] = 'attachment; filename="work_info_template.xlsx"' response["Content-Disposition"] = 'attachment; filename="work_info_template.xlsx"'
data_frame.to_excel(response, index=False) data_frame.to_excel(response, index=False)
create_work_info = False
if request.POST.get("create_work_info") == "true":
create_work_info = True
if request.method == "POST" and request.FILES.get("file") is not None:
total_count = 0
error_lists = []
success_lists = []
error_occured = False
file = request.FILES["file"]
file_extension = file.name.split(".")[-1].lower()
data_frame = (
pd.read_csv(file) if file_extension == "csv" else pd.read_excel(file)
)
work_info_dicts = data_frame.to_dict("records")
existing_badge_ids = set(Employee.objects.values_list("badge_id", flat=True))
existing_usernames = set(User.objects.values_list("username", flat=True))
existing_name_emails = set(
Employee.objects.values_list(
"employee_first_name", "employee_last_name", "email"
)
)
users = []
for work_info in work_info_dicts:
error = False
try:
email = work_info["Email"]
phone = work_info["Phone"]
first_name = convert_nan("First Name", work_info)
last_name = convert_nan("Last Name", work_info)
badge_id = work_info["Badge id"]
date_joining = work_info["Date joining"]
contract_end_date = work_info["Contract End Date"]
basic_salary = convert_nan("Basic Salary", work_info)
salary_hour = convert_nan("Salary Hour", work_info)
pattern = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
try:
if pd.isna(email) or not re.match(pattern, email):
work_info["Email Error"] = f"Invalid Email address"
error = True
except:
error = True
work_info["Email Error"] = f"Invalid Email address"
try:
pd.to_numeric(basic_salary)
except ValueError:
work_info["Basic Salary Error"] = f"Basic Salary must be a number"
error = True
try:
pd.to_numeric(salary_hour)
except ValueError:
work_info["Salary Hour Error"] = f"Salary Hour must be a number"
error = True
if pd.isna(first_name):
work_info["First Name error"] = f"First Name can't be empty"
error = True
if pd.isna(phone):
work_info["Phone error"] = f"Phone Number can't be empty"
error = True
name_email_tuple = (first_name, last_name, email)
if name_email_tuple in existing_name_emails:
work_info["Name and Email Error"] = (
"An employee with this first name, last name, and email already exists."
)
error = True
else:
existing_name_emails.add(name_email_tuple)
try:
pd.to_datetime(date_joining).date()
except:
work_info["Joining Date Error"] = (
f"Invalid Date format. Please use the format YYYY-MM-DD"
)
error = True
try:
pd.to_datetime(contract_end_date).date()
except:
work_info["Contract Error"] = (
f"Invalid Date format. Please use the format YYYY-MM-DD"
)
error = True
if badge_id in existing_badge_ids:
work_info["Badge ID Error"] = (
f"An Employee with the badge ID already exists"
)
error = True
else:
existing_badge_ids.add(badge_id)
if email in existing_usernames:
work_info["User ID Error"] = (
f"User with the email ID already exists"
)
error = True
else:
existing_usernames.add(email)
if error:
error_lists.append(work_info)
else:
success_lists.append(work_info)
except Exception as e:
error_occured = True
logger.error(e)
if create_work_info or not error_lists:
try:
users = bulk_create_user_import(success_lists)
employees = bulk_create_employee_import(success_lists)
thread = threading.Thread(
target=set_initial_password, args=(employees,)
)
thread.start()
total_count = len(employees)
bulk_create_department_import(success_lists)
bulk_create_job_position_import(success_lists)
bulk_create_job_role_import(success_lists)
bulk_create_work_types(success_lists)
bulk_create_shifts(success_lists)
bulk_create_employee_types(success_lists)
bulk_create_work_info_import(success_lists)
except Exception as e:
error_occured = True
logger.error(e)
if error_occured:
messages.error(request, "something went wrong....")
data_frame = pd.DataFrame(
["The provided titles don't match the default titles."],
columns=["Title Error"],
)
error_count = len(error_lists)
# Create an HTTP response object with the Excel file
response = HttpResponse(content_type="application/ms-excel")
response["Content-Disposition"] = 'attachment; filename="ImportError.xlsx"'
data_frame.to_excel(response, index=False)
response["X-Error-Count"] = error_count
return response
if error_lists:
for item in error_lists:
for key, value in error_data.items():
if key in item:
value.append(item[key])
else:
value.append(None)
keys_to_remove = [
key
for key, value in error_data.items()
if all(v is None for v in value)
]
for key in keys_to_remove:
del error_data[key]
data_frame = pd.DataFrame(error_data, columns=error_data.keys())
error_count = len(error_lists)
# Create an HTTP response object with the Excel file
response = HttpResponse(content_type="application/ms-excel")
response["Content-Disposition"] = 'attachment; filename="ImportError.xlsx"'
data_frame.to_excel(response, index=False)
response["X-Error-Count"] = error_count
return response
return JsonResponse(
{
"Success": "Employees Imported Succefully",
"success_count": total_count,
}
)
return response return response
@login_required
@hx_request_required
@permission_required("employee.add_employee")
def work_info_import(request):
if request.method == "GET":
return render(request, "employee/employee_import.html")
if request.method == "POST":
file = request.FILES.get("file")
if not file:
error_message = _("No file uploaded.")
return render(
request,
"employee/employee_import.html",
{"error_message": error_message},
)
file_extension = file.name.split(".")[-1].lower()
try:
if file_extension == "csv":
data_frame = pd.read_csv(file)
elif file_extension in ["xls", "xlsx"]:
data_frame = pd.read_excel(file)
else:
error_message = _(
"Unsupported file format. Please upload a CSV or Excel file."
)
return render(
request,
"employee/employee_import.html",
{"error_message": error_message},
)
valid, error_message = valid_import_file_headers(data_frame)
if not valid:
return render(
request,
"employee/employee_import.html",
{"error_message": error_message},
)
success_list, error_list, created_count = process_employee_records(
data_frame
)
if success_list:
try:
users = bulk_create_user_import(success_list)
employees = bulk_create_employee_import(success_list)
thread = threading.Thread(
target=set_initial_password, args=(employees,)
)
thread.start()
bulk_create_department_import(success_list)
bulk_create_job_position_import(success_list)
bulk_create_job_role_import(success_list)
bulk_create_work_types(success_list)
bulk_create_shifts(success_list)
bulk_create_employee_types(success_list)
bulk_create_work_info_import(success_list)
except Exception as e:
messages.error(request, _("Error Occured {}").format(e))
logger.error(e)
path_info = (
generate_error_report(
error_list, error_data_template, "EmployeesImportError.xlsx"
)
if error_list
else None
)
context = {
"created_count": created_count,
"total_count": created_count + len(error_list),
"error_count": len(error_list),
"model": _("Employees"),
"path_info": path_info,
}
result = render_to_string("import_popup.html", context)
result += """
<script>
$('#objectCreateModalTarget').css('max-width', '410px');
</script>
"""
return HttpResponse(result)
except Exception as e:
messages.error(
request,
_(
"Failed to read file. Please ensure it is a valid CSV or Excel file. : {}"
).format(e),
)
logger.error(f"File import error: {e}")
error_message = f"File import error: {e}"
return render(
request, "employee/employee_import.html", {"error_message": error_message}
)
@login_required @login_required
@manager_can_enter("employee.view_employee") @manager_can_enter("employee.view_employee")
def work_info_export(request): def work_info_export(request):
@@ -2686,9 +2576,19 @@ def work_info_export(request):
"export_form": EmployeeExportExcelForm(), "export_form": EmployeeExportExcelForm(),
} }
return render(request, "employee_export_filter.html", context) return render(request, "employee_export_filter.html", context)
employees_data = {} employees_data = {}
selected_columns = [] selected_columns = []
form = EmployeeExportExcelForm() form = EmployeeExportExcelForm()
field_overrides = {
"employee_work_info__department_id": "employee_work_info__department_id__department",
"employee_work_info__job_position_id": "employee_work_info__job_position_id__job_position",
"employee_work_info__job_role_id": "employee_work_info__job_role_id__job_role",
"employee_work_info__shift_id": "employee_work_info__shift_id__employee_shift",
"employee_work_info__work_type_id": "employee_work_info__work_type_id__work_type",
"employee_work_info__reporting_manager_id": "employee_work_info__reporting_manager_id__get_full_name",
"employee_work_info__employee_type_id": "employee_work_info__employee_type_id__employee_type",
}
employees = EmployeeFilter(request.GET).qs employees = EmployeeFilter(request.GET).qs
employees = filtersubordinatesemployeemodel( employees = filtersubordinatesemployeemodel(
request, employees, "employee.view_employee" request, employees, "employee.view_employee"
@@ -2699,54 +2599,61 @@ def work_info_export(request):
ids = request.GET.get("ids") ids = request.GET.get("ids")
id_list = json.loads(ids) id_list = json.loads(ids)
employees = Employee.objects.filter(id__in=id_list) employees = Employee.objects.filter(id__in=id_list)
for field in excel_columns:
value = field[0] prefetch_fields = list(set(f.split("__")[0] for f in selected_fields if "__" in f))
key = field[1] if prefetch_fields:
employees = employees.select_related(*prefetch_fields)
for value, key in excel_columns:
if value in selected_fields: if value in selected_fields:
selected_columns.append((value, key)) selected_columns.append((value, key))
for column_value, column_name in selected_columns:
nested_attributes = column_value.split("__") date_format = "YYYY-MM-DD"
employees_data[column_name] = [] user = request.user
for employee in employees: emp = getattr(user, "employee_get", None)
if emp:
info = EmployeeWorkInformation.objects.filter(employee_id=emp).first()
if info:
company = Company.objects.filter(company=info.company_id).first()
if company and company.date_format:
date_format = company.date_format
employees_data = {column_name: [] for _, column_name in selected_columns}
for employee in employees:
for column_value, column_name in selected_columns:
if column_value in field_overrides:
column_value = field_overrides[column_value]
nested_attrs = column_value.split("__")
value = employee value = employee
for attr in nested_attributes: for attr in nested_attrs:
value = getattr(value, attr, None) value = getattr(value, attr, None)
if value is None: if value is None:
break break
# Call the value if it's employee_work_info__reporting_manager_id__get_full_name
if callable(value):
try:
value = value()
except Exception:
value = ""
data = str(value) if value is not None else "" data = str(value) if value is not None else ""
if type(value) == date: if isinstance(value, date):
user = request.user try:
emp = user.employee_get data = value.strftime(
HORILLA_DATE_FORMATS.get(date_format, "%Y-%m-%d")
# Taking the company_name of the user
info = EmployeeWorkInformation.objects.filter(employee_id=emp)
if info.exists():
for i in info:
employee_company = i.company_id
company_name = Company.objects.filter(company=employee_company)
emp_company = company_name.first()
# Access the date_format attribute directly
date_format = (
emp_company.date_format if emp_company else "MMM. D, YYYY"
) )
else: except Exception:
date_format = "MMM. D, YYYY" data = str(value)
# Convert the string to a datetime.date object
start_date = datetime.strptime(str(value), "%Y-%m-%d").date()
# Print the formatted date for each format
for format_name, format_string in HORILLA_DATE_FORMATS.items():
if format_name == date_format:
data = start_date.strftime(format_string)
if data == "True": if data == "True":
data = _("Yes") data = _("Yes")
elif data == "False": elif data == "False":
data = _("No") data = _("No")
employees_data[column_name].append(data)
employees_data[column_name].append(data)
data_frame = pd.DataFrame(data=employees_data) data_frame = pd.DataFrame(data=employees_data)
response = HttpResponse(content_type="application/ms-excel") response = HttpResponse(content_type="application/ms-excel")
response["Content-Disposition"] = 'attachment; filename="employee_export.xlsx"' response["Content-Disposition"] = 'attachment; filename="employee_export.xlsx"'
@@ -2995,7 +2902,6 @@ def employee_select_filter(request):
request.GET, queryset=Employee.objects.filter() request.GET, queryset=Employee.objects.filter()
) )
# Get the filtered queryset
filtered_employees = filtersubordinatesemployeemodel( filtered_employees = filtersubordinatesemployeemodel(
request=request, queryset=employee_filter.qs, perm="employee.view_employee" request=request, queryset=employee_filter.qs, perm="employee.view_employee"
) )
@@ -3055,7 +2961,6 @@ def add_note(request, emp_id=None):
note.save() note.save()
note.note_files.set(attachment_ids) note.note_files.set(attachment_ids)
messages.success(request, _("Note added successfully..")) messages.success(request, _("Note added successfully.."))
response = render(request, "tabs/add_note.html", {"form": form})
return redirect(f"/employee/note-tab/{emp_id}") return redirect(f"/employee/note-tab/{emp_id}")
employee_obj = Employee.objects.get(id=emp_id) employee_obj = Employee.objects.get(id=emp_id)