[UPDT] EMPLOYEE: Updated employee import method
This commit is contained in:
@@ -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")),
|
||||||
|
|||||||
@@ -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()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
49
employee/templates/employee/employee_import.html
Normal file
49
employee/templates/employee/employee_import.html
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user