[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__reporting_manager_id", trans("Reporting Manager")),
("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__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_bank_details__bank_name", trans("Bank Name")),
("employee_bank_details__branch", trans("Branch")),

View File

@@ -3,14 +3,16 @@ employee/methods.py
"""
import logging
import re
import threading
from datetime import datetime
from datetime import date, datetime
from itertools import groupby
import pandas as pd
from django.apps import apps
from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import gettext as _
from base.context_processors import get_initial_prefix
from base.models import (
@@ -26,6 +28,72 @@ from employee.models import Employee, EmployeeWorkInformation
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):
"""
@@ -117,6 +185,172 @@ def check_relationship_with_employee_model(model):
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):
"""
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:
continue
badge_id = work_info["Badge id"]
badge_id = work_info["Badge ID"]
first_name = convert_nan("First Name", work_info)
last_name = convert_nan("Last Name", work_info)
phone = work_info["Phone"]
@@ -203,7 +437,7 @@ def set_initial_password(employees):
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.
@@ -212,21 +446,8 @@ def optimize_reporting_manager_lookup(success_lists):
single database query, and creates a dictionary for quick lookups based
on the full name of the reporting managers.
"""
# Step 1: Collect unique reporting manager names
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)
employees = Employee.objects.entire()
# 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 = {
f"{employee.employee_first_name} {employee.employee_last_name}": employee
for employee in employees
@@ -434,8 +655,7 @@ def bulk_create_work_info_import(success_lists):
new_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)
job_positions = set(row.get("Job Position") 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)
companies = set(row.get("Company") for row in success_lists)
# Bulk fetch related objects and reduce repeated DB calls
existing_employees = {
emp.badge_id: emp
for emp in Employee.objects.entire()
@@ -495,17 +714,25 @@ def bulk_create_work_info_import(success_lists):
comp.company: comp
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:
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"))
key = (
job_position_key = (
existing_departments.get(work_info.get("Department")),
work_info.get("Job Position"),
)
job_position_obj = existing_job_positions.get(key)
job_role_obj = existing_job_roles.get(work_info.get("Job Role"))
job_position_obj = existing_job_positions.get(job_position_key)
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"))
employee_type_obj = existing_employee_types.get(work_info.get("Employee Type"))
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
date_joining = (
work_info["Date joining"]
if not pd.isnull(work_info["Date joining"])
work_info["Date Joining"]
if not pd.isnull(work_info["Date Joining"])
else datetime.today()
)

View File

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

View File

@@ -1,321 +1,179 @@
var downloadMessages = {
ar: "هل ترغب في تنزيل القالب؟",
de: "Möchten Sie die Vorlage herunterladen?",
es: "¿Quieres descargar la plantilla?",
en: "Do you want to download the template?",
fr: "Voulez-vous télécharger le modèle ?",
ar: "هل ترغب في تنزيل القالب؟",
de: "Möchten Sie die Vorlage herunterladen?",
es: "¿Quieres descargar la plantilla?",
en: "Do you want to download the template?",
fr: "Voulez-vous télécharger le modèle ?",
};
var importSuccess = {
ar: "نجح الاستيراد", // Arabic
de: "Import erfolgreich", // German
es: "Importado con éxito", // Spanish
en: "Imported Successfully!", // English
fr: "Importation réussie", // French
ar: "نجح الاستيراد", // Arabic
de: "Import erfolgreich", // German
es: "Importado con éxito", // Spanish
en: "Imported Successfully!", // English
fr: "Importation réussie", // French
};
var uploadSuccess = {
ar: "تحميل كامل", // Arabic
de: "Upload abgeschlossen", // German
es: "Carga completa", // Spanish
en: "Upload Complete!", // English
fr: "Téléchargement terminé", // French
ar: "تحميل كامل", // Arabic
de: "Upload abgeschlossen", // German
es: "Carga completa", // Spanish
en: "Upload Complete!", // English
fr: "Téléchargement terminé", // French
};
var uploadingMessage = {
ar: "جارٍ الرفع",
de: "Hochladen...",
es: "Subiendo...",
en: "Uploading...",
fr: "Téléchargement en cours...",
ar: "جارٍ الرفع",
de: "Hochladen...",
es: "Subiendo...",
en: "Uploading...",
fr: "Téléchargement en cours...",
};
var validationMessage = {
ar: "يرجى تحميل ملف بامتداد .xlsx فقط.",
de: "Bitte laden Sie nur eine Datei mit der Erweiterung .xlsx hoch.",
es: "Por favor, suba un archivo con la extensión .xlsx solamente.",
en: "Please upload a file with the .xlsx extension only.",
fr: "Veuillez télécharger uniquement un fichier avec l'extension .xlsx.",
ar: "يرجى تحميل ملف بامتداد .xlsx فقط.",
de: "Bitte laden Sie nur eine Datei mit der Erweiterung .xlsx hoch.",
es: "Por favor, suba un archivo con la extensión .xlsx solamente.",
en: "Please upload a file with the .xlsx extension only.",
fr: "Veuillez télécharger uniquement un fichier avec l'extension .xlsx.",
};
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
let cookieValue = null;
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
}
return cookieValue;
return cookieValue;
}
function getCurrentLanguageCode(callback) {
var languageCode = $("#main-section-data").attr("data-lang");
var allowedLanguageCodes = ["ar", "de", "es", "en", "fr"];
if (allowedLanguageCodes.includes(languageCode)) {
callback(languageCode);
} else {
$.ajax({
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
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");
}
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");
},
});
}
},
error: function (xhr, textStatus, errorThrown) {
console.error("Error downloading file:", errorThrown);
},
});
});
}
}
function template_download(e) {
e.preventDefault();
var languageCode = null;
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();
e.preventDefault();
var languageCode = null;
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-file", 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.send();
}
xhr.onerror = function (e) {
console.error("Error downloading file:", e);
};
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 () {
$("#loading").show();
$("#loading").show();
});
$(document).ajaxStop(function () {
$("#loading").hide();
$("#loading").hide();
});
function simulateProgress() {
getCurrentLanguageCode(function (code) {
let progressBar = document.querySelector(".progress-bar");
let progressText = document.getElementById("progress-text");
getCurrentLanguageCode(function (code) {
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[languageCode] + 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);
});
}

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 %}
<style>
#progress {
width: 300px;
height: 30px;
border: 1px solid #ccc;
background-color: #f0f0f0;
margin-bottom: 10px;
position: relative;
overflow: hidden;
}
#progress {
width: 300px;
height: 30px;
border: 1px solid #ccc;
background-color: #f0f0f0;
margin-bottom: 10px;
position: relative;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: #4caf50;
position: absolute;
top: 0;
left: 0;
transition: width 0.2s ease-in-out;
}
.progress-bar {
height: 100%;
background-color: #4caf50;
position: absolute;
top: 0;
left: 0;
transition: width 0.2s ease-in-out;
}
#progress-text {
font-size: 18px;
font-weight: bold;
color: #333;
}
.custom-swal-container .swal2-styled {
display:block;
width:70%;
}
#progress-text {
font-size: 18px;
font-weight: bold;
color: #333;
}
.custom-swal-container .swal2-styled {
display: block;
width: 70%;
}
</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="employeeExportTarget" class="oh-modal__dialog" style="max-width: 750px" ></div>
<div id="employeeExportTarget" class="oh-modal__dialog" style="max-width: 750px"></div>
</div>
{% endif %}
{% if perms.employee.change_employee %}
<div
class="oh-modal"
id="bulkUpdateModal"
role="dialog"
aria-labelledby="bulkUpdateModal"
aria-hidden="true"
>
<div class="oh-modal__dialog" style="max-width: 750px">
<div class="oh-modal__dialog-header">
<h2 class="oh-modal__dialog-title" id="bulkUpdateModalLavel">
{% trans "Bulk Update Employees" %}
</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 pb-4" id="bulkUpdateModalBody">
<form
action="{%url 'employee-bulk-update' %}"
method="post"
onsubmit="event.stopPropagation();$(this).parents().find('.oh-modal--show').last().toggleClass('oh-modal--show');"
id="bulkUpdateModalForm"
class="oh-profile-section"
>
{% csrf_token %} {{update_fields_form.update_fields.label}}
{{update_fields_form.update_fields}}
{{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 class="oh-modal" id="bulkUpdateModal" role="dialog" aria-labelledby="bulkUpdateModal" aria-hidden="true">
<div class="oh-modal__dialog" style="max-width: 750px">
<div class="oh-modal__dialog-header">
<h2 class="oh-modal__dialog-title" id="bulkUpdateModalLavel">
{% trans "Bulk Update Employees" %}
</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 pb-4" id="bulkUpdateModalBody">
<form action="{%url 'employee-bulk-update' %}" method="post"
onsubmit="event.stopPropagation();$(this).parents().find('.oh-modal--show').last().toggleClass('oh-modal--show');"
id="bulkUpdateModalForm" class="oh-profile-section">
{% csrf_token %} {{update_fields_form.update_fields.label}}
{{update_fields_form.update_fields}}
{{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>
</div>
{% endif %}
<section class="oh-wrapper oh-main__topbar pb-3" x-data="{searchShow: false}">
<div class="oh-main__titlebar oh-main__titlebar--left">
<a
href="{% url 'employee-view' %}"
class="oh-main__titlebar-title fw-bold mb-0 text-dark"
hx-target="#view-container"
style="cursor: pointer"
>{% trans "Employees" %}</a
>
<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--left">
<a href="{% url 'employee-view' %}" class="oh-main__titlebar-title fw-bold mb-0 text-dark"
hx-target="#view-container" style="cursor: pointer">{% trans "Employees" %}</a>
<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">
<form
hx-get='{% url "employee-filter-view" %}'
id="filterForm"
hx-target="#view-container"
class="d-flex"
onsubmit="event.preventDefault()"
>
{% if emp %}
<div
class="oh-input-group oh-input__search-group"
:class="searchShow ? 'oh-input__search-group--show' : ''"
>
<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"
class="oh-input oh-input__icon"
aria-label="Search Input"
onkeyup="$('.filterButton')[0].click();if(this.value) {
$('.search_text').html(this.value)
$(this).parent().find('#dropdown').show()
}else{
$(this).parent().find('#dropdown').hide()
}"
onfocus="
if (this.value) {
$(this).parent().find('#dropdown').show()
}"
onfocusout="
setTimeout(function() {
$('#dropdown').hide()
}, 300);
"
/>
<input type="text" hidden name="search_field">
<div class="custom-dropdown" id="dropdown">
<ul class="search_content">
<li>
<a href="#" onclick="$('[name=search_field]').val('reporting_manager'); $('.filterButton')[0].click()">
{% trans "Search" %} <b>{% trans "Reporting Manager" %}</b> {% trans "for:" %}
<b class="search_text"></b>
</a>
</li>
<li>
<a href="#" onclick="$('[name=search_field]').val('department'); $('.filterButton')[0].click()">
{% trans "Search" %} <b>{% trans "Department" %}</b> {% trans "for:" %}
<b class="search_text"></b>
</a>
</li>
<li>
<a href="#" onclick="$('[name=search_field]').val('job_position'); $('.filterButton')[0].click()">
{% trans "Search" %} <b>{% trans "Job Position" %}</b> {% trans "for:" %}
<b class="search_text"></b>
</a>
</li>
<li>
<a href="#" onclick="$('[name=search_field]').val('job_role'); $('.filterButton')[0].click()">
{% trans "Search" %} <b>{% trans "Job Role" %}</b> {% trans "for:" %}
<b class="search_text"></b>
</a>
</li>
<li>
<a href="#" onclick="$('[name=search_field]').val('shift'); $('.filterButton')[0].click()">
{% trans "Search" %} <b>{% trans "Shift" %}</b> {% trans "for:" %}
<b class="search_text"></b>
</a>
</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>
<div class="oh-main__titlebar oh-main__titlebar--right">
<form hx-get='{% url "employee-filter-view" %}' id="filterForm" hx-target="#view-container" class="d-flex"
onsubmit="event.preventDefault()">
{% if emp %}
<div class="oh-input-group oh-input__search-group"
:class="searchShow ? 'oh-input__search-group--show' : ''">
<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"
class="oh-input oh-input__icon" aria-label="Search Input" onkeyup="$('.filterButton')[0].click();if(this.value) {
$('.search_text').html(this.value)
$(this).parent().find('#dropdown').show()
}else{
$(this).parent().find('#dropdown').hide()
}" onfocus="
if (this.value) {
$(this).parent().find('#dropdown').show()
}" onfocusout="
setTimeout(function() {
$('#dropdown').hide()
}, 300);
"
/>
<input type="text" hidden name="search_field">
<div class="custom-dropdown" id="dropdown">
<ul class="search_content">
<li>
<a href="#"
onclick="$('[name=search_field]').val('reporting_manager'); $('.filterButton')[0].click()">
{% trans "Search" %} <b>{% trans "Reporting Manager" %}</b> {% trans "for:" %}
<b class="search_text"></b>
</a>
</li>
<li>
<a href="#"
onclick="$('[name=search_field]').val('department'); $('.filterButton')[0].click()">
{% trans "Search" %} <b>{% trans "Department" %}</b> {% trans "for:" %}
<b class="search_text"></b>
</a>
</li>
<li>
<a href="#"
onclick="$('[name=search_field]').val('job_position'); $('.filterButton')[0].click()">
{% trans "Search" %} <b>{% trans "Job Position" %}</b> {% trans "for:" %}
<b class="search_text"></b>
</a>
</li>
<li>
<a href="#"
onclick="$('[name=search_field]').val('job_role'); $('.filterButton')[0].click()">
{% trans "Search" %} <b>{% trans "Job Role" %}</b> {% trans "for:" %}
<b class="search_text"></b>
</a>
</li>
<li>
<a href="#" onclick="$('[name=search_field]').val('shift'); $('.filterButton')[0].click()">
{% trans "Search" %} <b>{% trans "Shift" %}</b> {% trans "for:" %}
<b class="search_text"></b>
</a>
</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>
</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>
</ul>
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group">
<select
class="oh-select mt-1 w-100"
id="id_field"
name="field"
class="select2-selection select2-selection--single"
>
{% for field in gp_fields %}
<option value="{{ field.0 }}">{% trans field.1 %}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% if perms.employee.change_employee or perms.employee.add_employee or perms.employee.delete_employee %}
<div class="oh-btn-group ml-2" onclick="event.preventDefault();">
<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 %}
<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 %}
{% 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 %}
<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 class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group">
<select class="oh-select mt-1 w-100" id="id_field" name="field"
class="select2-selection select2-selection--single">
{% for field in gp_fields %}
<option value="{{ field.0 }}">{% trans field.1 %}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% if perms.employee.change_employee or perms.employee.add_employee or perms.employee.delete_employee %}
<div class="oh-btn-group ml-2" onclick="event.preventDefault();">
<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"
data-toggle="oh-modal-toggle" data-target="#objectCreateModal"
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 %}
</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 %}
</div>
</form>
</div>
</form>
</div>
</section>
<script>
function clearFilterFromTag(element) {
let field_id = element.attr("data-x-field");
$(`[name=${field_id}]`).val("");
$(`[name=${field_id}]`).change();
// Update all elements with the same ID to have null values
let elementId = $(`[name=${field_id}]:last`).attr("id");
let spanElement = $(
`.oh-dropdown__filter-body:first #select2-id_${field_id}-container, #select2-${elementId}-container`
);
if (spanElement.length) {
spanElement.attr("title", "---------");
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++;
function clearFilterFromTag(element) {
let field_id = element.attr("data-x-field");
$(`[name=${field_id}]`).val("");
$(`[name=${field_id}]`).change();
// Update all elements with the same ID to have null values
let elementId = $(`[name=${field_id}]:last`).attr("id");
let spanElement = $(
`.oh-dropdown__filter-body:first #select2-id_${field_id}-container, #select2-${elementId}-container`
);
if (spanElement.length) {
spanElement.attr("title", "---------");
spanElement.text("---------");
}
});
$("#filterCount").empty();
if (count > 0) {
$("#filterCount").text(`(${count})`);
}
$(".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");
$("#filterForm").submit(function (e) {
filterFormSubmit("filterForm");
$(`[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++;
}
});
$("#filterCount").empty();
if (count > 0) {
$("#filterCount").text(`(${count})`);
}
}
$("#filterForm").submit(function (e) {
filterFormSubmit("filterForm");
});
});
});
</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-export", views.employee_export, name="employee-export"),
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("get-birthday", views.get_employees_birthday, name="get-birthday"),
path("dashboard", views.dashboard, name="dashboard"),

View File

@@ -16,7 +16,6 @@ import calendar
import json
import operator
import os
import re
import threading
from datetime import date, datetime, timedelta
from urllib.parse import parse_qs
@@ -28,12 +27,13 @@ from django.contrib import messages
from django.contrib.auth.models import User
from django.core.cache import cache
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.query import QuerySet
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.template.loader import render_to_string
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext as __
@@ -57,17 +57,14 @@ from base.models import (
Company,
Department,
EmailLog,
EmployeeShift,
EmployeeType,
JobPosition,
JobRole,
RotatingShiftAssign,
RotatingWorkTypeAssign,
ShiftRequest,
WorkType,
WorkTypeRequest,
clear_messages,
)
from base.views import generate_error_report
from employee.filters import DocumentRequestFilter, EmployeeFilter, EmployeeReGroup
from employee.forms import (
BonusPointAddForm,
@@ -94,9 +91,11 @@ from employee.methods.methods import (
bulk_create_user_import,
bulk_create_work_info_import,
bulk_create_work_types,
convert_nan,
error_data_template,
get_ordered_badge_ids,
process_employee_records,
set_initial_password,
valid_import_file_headers,
)
from employee.models import (
BonusPoint,
@@ -191,7 +190,6 @@ def _check_reporting_manager(request, *args, **kwargs):
return request.user.employee_get.reporting_manager.exists()
# Create your views here.
@login_required
def get_language_code(request):
"""
@@ -1885,11 +1883,13 @@ def employee_bulk_delete(request):
"""
This method is used to delete set of Employee instances
"""
ids = request.POST["ids"]
ids = json.loads(ids)
for employee_id in ids:
ids = json.loads(request.POST.get("ids", "[]"))
if not 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:
employee = Employee.objects.get(id=employee_id)
if apps.is_installed("payroll"):
if employee.contract_set.all().exists():
contracts = employee.contract_set.all()
@@ -1898,16 +1898,19 @@ def employee_bulk_delete(request):
contract.delete()
user = employee.employee_user_id
user.delete()
messages.success(
request, _("%(employee)s deleted.") % {"employee": employee}
)
deleted_count += 1
except Employee.DoesNotExist:
messages.error(request, _("Employee not found."))
except ProtectedError:
messages.error(
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"})
@@ -2419,19 +2422,19 @@ def convert_nan(field, dicts):
try:
float(field_value)
return None
except ValueError:
except (ValueError, TypeError):
return field_value
@login_required
@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(
columns=[
"Badge id",
"Badge ID",
"First Name",
"Last Name",
"Phone",
@@ -2446,234 +2449,121 @@ def work_info_import(request):
"Reporting Manager",
"Company",
"Location",
"Date joining",
"Date Joining",
"Contract End Date",
"Basic Salary",
"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["Content-Disposition"] = 'attachment; filename="work_info_template.xlsx"'
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
@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
@manager_can_enter("employee.view_employee")
def work_info_export(request):
@@ -2686,9 +2576,19 @@ def work_info_export(request):
"export_form": EmployeeExportExcelForm(),
}
return render(request, "employee_export_filter.html", context)
employees_data = {}
selected_columns = []
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 = filtersubordinatesemployeemodel(
request, employees, "employee.view_employee"
@@ -2699,54 +2599,61 @@ def work_info_export(request):
ids = request.GET.get("ids")
id_list = json.loads(ids)
employees = Employee.objects.filter(id__in=id_list)
for field in excel_columns:
value = field[0]
key = field[1]
prefetch_fields = list(set(f.split("__")[0] for f in selected_fields if "__" in f))
if prefetch_fields:
employees = employees.select_related(*prefetch_fields)
for value, key in excel_columns:
if value in selected_fields:
selected_columns.append((value, key))
for column_value, column_name in selected_columns:
nested_attributes = column_value.split("__")
employees_data[column_name] = []
for employee in employees:
date_format = "YYYY-MM-DD"
user = request.user
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
for attr in nested_attributes:
for attr in nested_attrs:
value = getattr(value, attr, None)
if value is None:
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 ""
if type(value) == date:
user = request.user
emp = user.employee_get
# 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"
if isinstance(value, date):
try:
data = value.strftime(
HORILLA_DATE_FORMATS.get(date_format, "%Y-%m-%d")
)
else:
date_format = "MMM. D, YYYY"
# 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)
except Exception:
data = str(value)
if data == "True":
data = _("Yes")
elif data == "False":
data = _("No")
employees_data[column_name].append(data)
employees_data[column_name].append(data)
data_frame = pd.DataFrame(data=employees_data)
response = HttpResponse(content_type="application/ms-excel")
response["Content-Disposition"] = 'attachment; filename="employee_export.xlsx"'
@@ -2995,7 +2902,6 @@ def employee_select_filter(request):
request.GET, queryset=Employee.objects.filter()
)
# Get the filtered queryset
filtered_employees = filtersubordinatesemployeemodel(
request=request, queryset=employee_filter.qs, perm="employee.view_employee"
)
@@ -3055,7 +2961,6 @@ def add_note(request, emp_id=None):
note.save()
note.note_files.set(attachment_ids)
messages.success(request, _("Note added successfully.."))
response = render(request, "tabs/add_note.html", {"form": form})
return redirect(f"/employee/note-tab/{emp_id}")
employee_obj = Employee.objects.get(id=emp_id)