This commit introduces significant changes to the architecture of the Horilla HRMS system by decoupling interdependent modules. The following modifications were made: 1. **Module Independence**: Each module has been refactored to eliminate reliance on other modules, promoting a more modular and maintainable codebase. 2. **Refactored Imports and Dependencies**: Adjusted import statements and dependency injections to support independent module operation. 3. **Compatibility and Functionality**: Ensured that all modules are compatible with existing systems and maintain their intended functionality both independently and when integrated with other modules. These changes enhance the modularity, maintainability, and scalability of the Horilla HRMS, allowing developers to work on individual modules without affecting the entire system. Future development and deployment will be more efficient and less prone to issues arising from tightly coupled code. **NOTE** For existing Horilla users, if you face any issues during the migrations, please run the following command and try again the migrations. - `python3 manage.py makemigrations` - `python3 manage.py migrate base` - `python3 manage.py migrate` * [IMP] ASSET: Asset module dependency removal from other Horilla apps * [IMP] ATTENDANCE: Attendance module dependency removal from other Horilla apps * [IMP] BASE: Base module dependency removal from other Horilla apps * [IMP] EMPLOYEE: Employee module dependency removal from other Horilla apps * [IMP] HELPDESK: Helpdesk module dependency removal from other Horilla apps * [IMP] HORILLA AUDIT: Horilla Audit module dependency removal from other Horilla apps * [IMP] HORILLA CRUMBS: Horilla Crumbs module dependency removal from other Horilla apps * [IMP] HORILLA AUTOMATIONS: Horilla Automations module dependency removal from other Horilla apps * [IMP] HORILLA VIEWS: Horilla Views module dependency removal from other Horilla apps * [IMP] LEAVE: Leave module dependency removal from other Horilla apps * [IMP] OFFBOARDING: Offboarding module dependency removal from other Horilla apps * [IMP] ONBOARDING: Onboarding module dependency removal from other Horilla apps * [IMP] PMS: PMS module dependency removal from other Horilla apps * [IMP] PAYROLL: Payroll module dependency removal from other Horilla apps * [IMP] RECRUITMENT: Recruitment module dependency removal from other Horilla apps * [IMP] HORILLA: Dependency removal updates * [IMP] TEMPLATES: Dependency removal updates * [IMP] STATIC: Dependency removal updates * [IMP] HORILLA DOCUMENTS: Horilla Documents module dependency removal from other Horilla apps * [ADD] HORILLA: methods.py * [UPDT] HORILLA: Settings.py * [FIX] EMPLOYEE: About tab issue * Update horilla_settings.py * Remove dummy db init password
1078 lines
37 KiB
Python
1078 lines
37 KiB
Python
"""
|
|
forms.py
|
|
|
|
This module contains the form classes used in the application.
|
|
|
|
Each form represents a specific functionality or data input in the
|
|
application. They are responsible for validating
|
|
and processing user input data.
|
|
|
|
Classes:
|
|
- YourForm: Represents a form for handling specific data input.
|
|
|
|
Usage:
|
|
from django import forms
|
|
|
|
class YourForm(forms.Form):
|
|
field_name = forms.CharField()
|
|
|
|
def clean_field_name(self):
|
|
# Custom validation logic goes here
|
|
pass
|
|
"""
|
|
|
|
import datetime
|
|
import json
|
|
import logging
|
|
import uuid
|
|
from calendar import month_name
|
|
from collections import OrderedDict
|
|
from typing import Any, Dict
|
|
|
|
from django import forms
|
|
from django.apps import apps
|
|
from django.core.exceptions import ValidationError
|
|
from django.db.models.query import QuerySet
|
|
from django.forms import DateTimeInput
|
|
from django.template.loader import render_to_string
|
|
from django.utils.html import format_html
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from attendance.filters import AttendanceFilters
|
|
from attendance.models import (
|
|
Attendance,
|
|
AttendanceActivity,
|
|
AttendanceLateComeEarlyOut,
|
|
AttendanceOverTime,
|
|
AttendanceRequestComment,
|
|
AttendanceRequestFile,
|
|
AttendanceValidationCondition,
|
|
GraceTime,
|
|
WorkRecords,
|
|
attendance_date_validate,
|
|
strtime_seconds,
|
|
validate_time_format,
|
|
)
|
|
from base.forms import MultipleFileField
|
|
from base.methods import get_working_days, reload_queryset
|
|
from base.models import Company, EmployeeShift
|
|
from employee.filters import EmployeeFilter
|
|
from employee.models import Employee
|
|
from horilla import horilla_middlewares
|
|
from horilla_widgets.widgets.horilla_multi_select_field import HorillaMultiSelectField
|
|
from horilla_widgets.widgets.select_widgets import HorillaMultiSelectWidget
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ModelForm(forms.ModelForm):
|
|
"""
|
|
Overriding django default model form to apply some styles
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
reload_queryset(self.fields)
|
|
request = getattr(horilla_middlewares._thread_locals, "request", None)
|
|
|
|
for field_name, field in self.fields.items():
|
|
widget = field.widget
|
|
if isinstance(widget, (forms.DateInput)):
|
|
field.initial = datetime.date.today()
|
|
|
|
if isinstance(
|
|
widget, (forms.NumberInput, forms.EmailInput, forms.TextInput)
|
|
):
|
|
label = _(field.label.title())
|
|
|
|
field.widget.attrs.update(
|
|
{"class": "oh-input w-100", "placeholder": label}
|
|
)
|
|
elif isinstance(widget, (forms.Select,)):
|
|
label = ""
|
|
if field.label is not None:
|
|
label = _(field.label)
|
|
field.empty_label = _("---Choose {label}---").format(label=label)
|
|
self.fields[field_name].widget.attrs.update(
|
|
{
|
|
"class": "oh-select oh-select-2 w-100",
|
|
"id": uuid.uuid4(),
|
|
"style": "height:50px;border-radius:0;",
|
|
}
|
|
)
|
|
elif isinstance(widget, (forms.Textarea)):
|
|
label = _(field.label)
|
|
field.widget.attrs.update(
|
|
{
|
|
"class": "oh-input w-100",
|
|
"placeholder": label,
|
|
"rows": 2,
|
|
"cols": 40,
|
|
}
|
|
)
|
|
elif isinstance(
|
|
widget,
|
|
(
|
|
forms.CheckboxInput,
|
|
forms.CheckboxSelectMultiple,
|
|
),
|
|
):
|
|
field.widget.attrs.update({"class": "oh-switch__checkbox"})
|
|
if isinstance(widget, forms.DateInput):
|
|
field.widget = forms.DateInput(
|
|
attrs={"type": "date", "class": "oh-input w-100"}
|
|
)
|
|
if isinstance(widget, forms.TimeInput):
|
|
field.widget = forms.DateInput(
|
|
attrs={"type": "time", "class": "oh-input w-100"}
|
|
)
|
|
|
|
try:
|
|
self.fields["employee_id"].initial = request.user.employee_get
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
self.fields["company_id"].initial = (
|
|
request.user.employee_get.get_company
|
|
)
|
|
except:
|
|
pass
|
|
|
|
|
|
class AttendanceUpdateForm(ModelForm):
|
|
"""
|
|
This model form is used to direct save the validated query dict to attendance model
|
|
from AttendanceForm. This form can be used to update existing attendance.
|
|
"""
|
|
|
|
class Meta:
|
|
"""
|
|
Meta class to add the additional info
|
|
"""
|
|
|
|
fields = "__all__"
|
|
exclude = [
|
|
"overtime_second",
|
|
"at_work_second",
|
|
"attendance_day",
|
|
"request_description",
|
|
"approved_overtime_second",
|
|
"request_type",
|
|
"requested_data",
|
|
"is_validate_request",
|
|
"is_validate_request_approved",
|
|
"attendance_overtime",
|
|
"is_active",
|
|
]
|
|
model = Attendance
|
|
widgets = {
|
|
"attendance_clock_in": DateTimeInput(attrs={"type": "time"}),
|
|
"attendance_clock_out": DateTimeInput(attrs={"type": "time"}),
|
|
"attendance_clock_out_date": DateTimeInput(attrs={"type": "date"}),
|
|
"attendance_date": DateTimeInput(attrs={"type": "date"}),
|
|
"attendance_clock_in_date": DateTimeInput(attrs={"type": "date"}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
if instance := kwargs.get("instance"):
|
|
# django forms not showing value inside the date, time html element.
|
|
# so here overriding default forms instance method to set initial value
|
|
condition = AttendanceValidationCondition.objects.first()
|
|
condition = (
|
|
strtime_seconds(condition.minimum_overtime_to_approve)
|
|
if condition and condition.minimum_overtime_to_approve
|
|
else 0
|
|
)
|
|
initial = {
|
|
"attendance_date": instance.attendance_date.strftime("%Y-%m-%d"),
|
|
"attendance_clock_in": instance.attendance_clock_in.strftime("%H:%M"),
|
|
"attendance_clock_in_date": instance.attendance_clock_in_date.strftime(
|
|
"%Y-%m-%d"
|
|
),
|
|
}
|
|
if instance.attendance_clock_out_date is not None:
|
|
initial["attendance_clock_out"] = (
|
|
instance.attendance_clock_out.strftime("%H:%M")
|
|
)
|
|
initial["attendance_clock_out_date"] = (
|
|
instance.attendance_clock_out_date.strftime("%Y-%m-%d")
|
|
)
|
|
kwargs["initial"] = initial
|
|
super().__init__(*args, **kwargs)
|
|
self.fields["employee_id"].widget.attrs.update({"id": str(uuid.uuid4())})
|
|
self.fields["shift_id"].widget.attrs.update(
|
|
{
|
|
"id": str(uuid.uuid4()),
|
|
"onchange": "shiftChange($(this))",
|
|
}
|
|
)
|
|
self.fields["attendance_date"].widget.attrs.update(
|
|
{
|
|
"onchange": "attendanceDateChange($(this))",
|
|
}
|
|
)
|
|
self.fields["work_type_id"].widget.attrs.update({"id": str(uuid.uuid4())})
|
|
|
|
self.fields["attendance_overtime_approve"].label = _("Approve overtime?")
|
|
self.fields["attendance_validated"].label = _("Validate Attendance?")
|
|
if instance is not None and (
|
|
strtime_seconds(instance.attendance_overtime) < condition
|
|
or not instance.attendance_validated
|
|
):
|
|
del self.fields["attendance_overtime_approve"]
|
|
|
|
def as_p(self, *args, **kwargs):
|
|
"""
|
|
Render the form fields as HTML table rows with Bootstrap styling.
|
|
"""
|
|
context = {"form": self}
|
|
table_html = render_to_string("attendance_form.html", context)
|
|
return table_html
|
|
|
|
|
|
class AttendanceForm(ModelForm):
|
|
"""
|
|
Model form for Attendance model
|
|
"""
|
|
|
|
employee_id = HorillaMultiSelectField(
|
|
queryset=Employee.objects.filter(employee_work_info__isnull=False),
|
|
widget=HorillaMultiSelectWidget(
|
|
filter_route_name="employee-widget-filter",
|
|
filter_class=EmployeeFilter,
|
|
filter_instance_contex_name="f",
|
|
filter_template_path="employee_filters.html",
|
|
),
|
|
label=_("Employees"),
|
|
)
|
|
|
|
class Meta:
|
|
"""
|
|
Meta class to add the additional info
|
|
"""
|
|
|
|
model = Attendance
|
|
fields = "__all__"
|
|
exclude = [
|
|
"attendance_overtime_approve",
|
|
"attendance_overtime_calculation",
|
|
"at_work_second",
|
|
"overtime_second",
|
|
"attendance_day",
|
|
"request_description",
|
|
"approved_overtime_second",
|
|
"request_type",
|
|
"requested_data",
|
|
"is_validate_request",
|
|
"is_validate_request_approved",
|
|
"attendance_overtime",
|
|
"is_active",
|
|
]
|
|
widgets = {
|
|
"attendance_clock_in": DateTimeInput(attrs={"type": "time"}),
|
|
"attendance_clock_out": DateTimeInput(attrs={"type": "time"}),
|
|
"attendance_clock_out_date": DateTimeInput(attrs={"type": "date"}),
|
|
"attendance_date": DateTimeInput(attrs={"type": "date"}),
|
|
"attendance_clock_in_date": DateTimeInput(attrs={"type": "date"}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
initial = {
|
|
"attendance_clock_out_date": datetime.datetime.today()
|
|
.date()
|
|
.strftime("%Y-%m-%d"),
|
|
"attendance_clock_out": datetime.datetime.today().time().strftime("%H:%M"),
|
|
}
|
|
if instance := kwargs.get("instance"):
|
|
# django forms not showing value inside the date, time html element.
|
|
# so here overriding default forms instance method to set initial value
|
|
initial = {
|
|
"attendance_date": instance.attendance_date.strftime("%Y-%m-%d"),
|
|
"attendance_clock_in": instance.attendance_clock_in.strftime("%H:%M"),
|
|
"attendance_clock_in_date": instance.attendance_clock_in_date.strftime(
|
|
"%Y-%m-%d"
|
|
),
|
|
}
|
|
if instance.attendance_clock_out_date is not None:
|
|
initial["attendance_clock_out"] = (
|
|
instance.attendance_clock_out.strftime("%H:%M")
|
|
)
|
|
initial["attendance_clock_out_date"] = (
|
|
instance.attendance_clock_out_date.strftime("%Y-%m-%d")
|
|
)
|
|
kwargs["initial"] = initial
|
|
super().__init__(*args, **kwargs)
|
|
reload_queryset(self.fields)
|
|
self.fields["employee_id"].widget.attrs.update({"id": str(uuid.uuid4())})
|
|
self.fields["shift_id"].widget.attrs.update(
|
|
{
|
|
"id": str(uuid.uuid4()),
|
|
"onchange": "shiftChange($(this))",
|
|
}
|
|
)
|
|
self.fields["attendance_date"].widget.attrs.update(
|
|
{
|
|
"onchange": "attendanceDateChange($(this))",
|
|
}
|
|
)
|
|
self.fields["work_type_id"].widget.attrs.update({"id": str(uuid.uuid4())})
|
|
|
|
def save(self, commit=True):
|
|
instance = super().save(commit=False)
|
|
for emp_id in self.data.getlist("employee_id"):
|
|
if int(emp_id) != int(instance.employee_id.id):
|
|
data_copy = self.data.copy()
|
|
data_copy.update({"employee_id": str(emp_id)})
|
|
attendance = AttendanceUpdateForm(data_copy).save(commit=False)
|
|
attendance.save()
|
|
if commit:
|
|
instance.save()
|
|
return instance
|
|
|
|
def as_p(self, *args, **kwargs):
|
|
"""
|
|
Render the form fields as HTML table rows with Bootstrap styling.
|
|
"""
|
|
context = {"form": self}
|
|
table_html = render_to_string("attendance_form.html", context)
|
|
return table_html
|
|
|
|
def clean(self) -> Dict[str, Any]:
|
|
super().clean()
|
|
self.instance.employee_id = Employee.objects.filter(
|
|
id=self.data.get("employee_id")
|
|
).first()
|
|
|
|
self.errors.pop("employee_id", None)
|
|
if self.instance.employee_id is None:
|
|
raise ValidationError({"employee_id": _("This field is required")})
|
|
super().clean()
|
|
employee_ids = self.data.getlist("employee_id")
|
|
existing_attendance = Attendance.objects.filter(
|
|
attendance_date=self.data["attendance_date"]
|
|
).filter(employee_id__id__in=employee_ids)
|
|
if existing_attendance.exists():
|
|
raise ValidationError(
|
|
{
|
|
"employee_id": f"""Already attendance exists for {list(existing_attendance.values_list("employee_id__employee_first_name",flat=True))} employees"""
|
|
}
|
|
)
|
|
|
|
def clean_employee_id(self):
|
|
"""
|
|
Used to validate employee_id field
|
|
"""
|
|
employee = self.cleaned_data["employee_id"]
|
|
for emp in employee:
|
|
attendance = Attendance.objects.filter(
|
|
employee_id=emp, attendance_date=self.data["attendance_date"]
|
|
).first()
|
|
if attendance is not None:
|
|
raise ValidationError(
|
|
_(
|
|
"Attendance for the date is already exist for %(emp)s"
|
|
% {"emp": emp}
|
|
)
|
|
)
|
|
if employee.first() is None:
|
|
raise ValidationError(_("Employee not chosen"))
|
|
|
|
return employee.first()
|
|
|
|
|
|
class AttendanceActivityForm(ModelForm):
|
|
"""
|
|
Model form for AttendanceActivity model
|
|
"""
|
|
|
|
class Meta:
|
|
"""
|
|
Meta class to add the additional info
|
|
"""
|
|
|
|
model = AttendanceActivity
|
|
fields = "__all__"
|
|
widgets = {
|
|
"clock_in": DateTimeInput(attrs={"type": "time"}),
|
|
"clock_out": DateTimeInput(attrs={"type": "time"}),
|
|
"clock_in_date": DateTimeInput(attrs={"type": "date"}),
|
|
"clock_out_date": DateTimeInput(attrs={"type": "date"}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
if instance := kwargs.get("instance"):
|
|
# django forms not showing value inside the date, time html element.
|
|
# so here overriding default forms instance method to set initial value
|
|
|
|
initial = {
|
|
"attendance_date": instance.attendance_date.strftime("%Y-%m-%d"),
|
|
"clock_in_date": instance.clock_in_date.strftime("%Y-%m-%d"),
|
|
"clock_in": instance.clock_in.strftime("%H:%M"),
|
|
}
|
|
if instance.clock_out is not None:
|
|
initial["clock_out"] = instance.clock_out.strftime("%H:%M")
|
|
initial["clock_out_date"] = instance.clock_out_date.strftime("%Y-%m-%d")
|
|
kwargs["initial"] = initial
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
class MonthSelectField(forms.ChoiceField):
|
|
"""
|
|
Generate month choices
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
choices = [
|
|
(month_name[i].lower(), _(month_name[i].capitalize())) for i in range(1, 13)
|
|
]
|
|
super().__init__(choices=choices, *args, **kwargs)
|
|
|
|
|
|
class AttendanceOverTimeForm(ModelForm):
|
|
"""
|
|
Model form for AttendanceOverTime model
|
|
"""
|
|
|
|
month = MonthSelectField(label=_("Month"))
|
|
|
|
class Meta:
|
|
"""
|
|
Meta class to add the additional info
|
|
"""
|
|
|
|
model = AttendanceOverTime
|
|
fields = "__all__"
|
|
exclude = [
|
|
"hour_account_second",
|
|
"overtime_second",
|
|
"month_sequence",
|
|
"hour_pending_second",
|
|
"is_active",
|
|
]
|
|
labels = {
|
|
"employee_id": _("Employee"),
|
|
"year": _("Year"),
|
|
"worked_hours": _("Worked Hours"),
|
|
"pending_hours": _("Pending Hours"),
|
|
"overtime": _("Overtime"),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.fields["employee_id"].widget.attrs.update({"id": str(uuid.uuid4())})
|
|
|
|
def as_p(self, *args, **kwargs):
|
|
"""
|
|
Render the form fields as HTML table rows with Bootstrap styling.
|
|
"""
|
|
context = {"form": self}
|
|
table_html = render_to_string("attendance_form.html", context)
|
|
return table_html
|
|
|
|
|
|
class AttendanceLateComeEarlyOutForm(ModelForm):
|
|
"""
|
|
Model form for attendance AttendanceLateComeEarlyOut
|
|
"""
|
|
|
|
class Meta:
|
|
"""
|
|
Meta class to add the additional info
|
|
"""
|
|
|
|
model = AttendanceLateComeEarlyOut
|
|
fields = "__all__"
|
|
|
|
|
|
class AttendanceValidationConditionForm(forms.ModelForm):
|
|
"""
|
|
Model form for AttendanceValidationCondition
|
|
"""
|
|
|
|
class Meta:
|
|
"""
|
|
Meta class to add the additional info
|
|
"""
|
|
|
|
model = AttendanceValidationCondition
|
|
validation_at_work = forms.DurationField()
|
|
approve_overtime_after = forms.DurationField()
|
|
overtime_cutoff = forms.DurationField()
|
|
company_id = forms.ModelMultipleChoiceField(
|
|
queryset=Company.objects.all(),
|
|
widget=forms.SelectMultiple(attrs={"class": "oh-select oh-select-2 w-100"}),
|
|
)
|
|
widgets = {
|
|
"validation_at_work": forms.TextInput(
|
|
attrs={"class": "oh-input w-100", "placeholder": "09:00"}
|
|
),
|
|
"minimum_overtime_to_approve": forms.TextInput(
|
|
attrs={"class": "oh-input w-100", "placeholder": "00:30"}
|
|
),
|
|
"overtime_cutoff": forms.TextInput(
|
|
attrs={"class": "oh-input w-100", "placeholder": "02:00"}
|
|
),
|
|
"company_id": forms.SelectMultiple(attrs={"class": "oh-select w-100"}),
|
|
}
|
|
|
|
labels = {
|
|
"validation_at_work": format_html(
|
|
_(
|
|
"<span title='Do not Auto Validate Attendance if an Employee Works More Than this Amount of Duration'>{}</span>"
|
|
),
|
|
_("Maximum Allowed working hours"),
|
|
),
|
|
"minimum_overtime_to_approve": _("Minimum Hour to Approve Overtime"),
|
|
"overtime_cutoff": _("Maximum Allowed Overtime Per Day"),
|
|
}
|
|
fields = "__all__"
|
|
exclude = ["is_active"]
|
|
|
|
|
|
class AttendanceRequestForm(ModelForm):
|
|
"""
|
|
AttendanceRequestForm
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
if instance := kwargs.get("instance"):
|
|
# django forms not showing value inside the date, time html element.
|
|
# so here overriding default forms instance method to set initial value
|
|
initial = {
|
|
"attendance_date": instance.attendance_date.strftime("%Y-%m-%d"),
|
|
"attendance_clock_in": instance.attendance_clock_in.strftime("%H:%M"),
|
|
"attendance_clock_in_date": instance.attendance_clock_in_date.strftime(
|
|
"%Y-%m-%d"
|
|
),
|
|
}
|
|
if instance.attendance_clock_out_date is not None:
|
|
initial["attendance_clock_out"] = (
|
|
instance.attendance_clock_out.strftime("%H:%M")
|
|
)
|
|
initial["attendance_clock_out_date"] = (
|
|
instance.attendance_clock_out_date.strftime("%Y-%m-%d")
|
|
)
|
|
kwargs["initial"] = initial
|
|
super().__init__(*args, **kwargs)
|
|
self.fields["attendance_clock_out_date"].required = False
|
|
self.fields["attendance_clock_out"].required = False
|
|
self.fields["shift_id"].widget.attrs.update(
|
|
{
|
|
"id": str(uuid.uuid4()),
|
|
"onchange": "shiftChange($(this))",
|
|
}
|
|
)
|
|
self.fields["attendance_date"].widget.attrs.update(
|
|
{
|
|
"onchange": "attendanceDateChange($(this))",
|
|
}
|
|
)
|
|
self.fields["work_type_id"].widget.attrs.update({"id": str(uuid.uuid4())})
|
|
|
|
class Meta:
|
|
"""
|
|
Meta class for additional options
|
|
"""
|
|
|
|
model = Attendance
|
|
fields = [
|
|
"attendance_date",
|
|
"shift_id",
|
|
"work_type_id",
|
|
"attendance_clock_in_date",
|
|
"attendance_clock_in",
|
|
"attendance_clock_out_date",
|
|
"attendance_clock_out",
|
|
"attendance_worked_hour",
|
|
"minimum_hour",
|
|
"request_description",
|
|
]
|
|
|
|
def as_p(self, *args, **kwargs):
|
|
"""
|
|
Render the form fields as HTML table rows with Bootstrap styling.
|
|
"""
|
|
context = {"form": self}
|
|
table_html = render_to_string("attendance_form.html", context)
|
|
return table_html
|
|
|
|
def save(self, commit: bool = ...) -> Any:
|
|
# No need to save the changes to the actual modal instance
|
|
return super().save(False)
|
|
|
|
|
|
class NewRequestForm(AttendanceRequestForm):
|
|
"""
|
|
NewRequestForm class
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Add the new model choice field to the form at the beginning
|
|
old_dict = self.fields
|
|
new_dict = {
|
|
"employee_id": forms.ModelChoiceField(
|
|
queryset=Employee.objects.filter(is_active=True),
|
|
label=_("Employee"),
|
|
widget=forms.Select(
|
|
attrs={
|
|
"class": "oh-select oh-select-2 w-100",
|
|
"hx-target": "#id_shift_id_div",
|
|
"hx-get": "/attendance/get-employee-shift?bulk=False",
|
|
"hx-trigger": "change",
|
|
}
|
|
),
|
|
),
|
|
"create_bulk": forms.BooleanField(
|
|
required=False,
|
|
label=_("Create Bulk"),
|
|
widget=forms.CheckboxInput(
|
|
attrs={
|
|
"class": "oh-checkbox",
|
|
"hx-target": "#objectCreateModalTarget",
|
|
"hx-get": "/attendance/request-new-attendance?bulk=True",
|
|
"hx-trigger": "change",
|
|
}
|
|
),
|
|
),
|
|
}
|
|
self.fields["request_description"].label = _("Request description")
|
|
new_dict.update(old_dict)
|
|
self.fields = new_dict
|
|
|
|
def as_p(self, *args, **kwargs):
|
|
"""
|
|
Render the form fields as HTML table rows with Bootstrap styling.
|
|
"""
|
|
context = {"form": self}
|
|
form_html = render_to_string(
|
|
"requests/attendance/request_new_form.html", context
|
|
)
|
|
return form_html
|
|
|
|
def clean(self) -> Dict[str, Any]:
|
|
super().clean()
|
|
|
|
employee = self.cleaned_data["employee_id"]
|
|
attendance_date = self.cleaned_data["attendance_date"]
|
|
attendances = Attendance.objects.filter(
|
|
employee_id=employee, attendance_date=attendance_date
|
|
)
|
|
if employee and not hasattr(employee, "employee_work_info"):
|
|
raise ValidationError(_("Employee work info not found"))
|
|
data = {
|
|
"employee_id": employee,
|
|
"attendance_date": attendance_date,
|
|
"attendance_clock_in_date": self.cleaned_data["attendance_clock_in_date"],
|
|
"attendance_clock_in": self.cleaned_data["attendance_clock_in"],
|
|
"attendance_clock_out": self.cleaned_data["attendance_clock_out"],
|
|
"attendance_clock_out_date": self.cleaned_data["attendance_clock_out_date"],
|
|
"shift_id": self.cleaned_data["shift_id"],
|
|
"work_type_id": self.cleaned_data["work_type_id"],
|
|
"attendance_worked_hour": self.cleaned_data["attendance_worked_hour"],
|
|
"minimum_hour": self.data["minimum_hour"],
|
|
}
|
|
if attendances.exists():
|
|
data["employee_id"] = employee.id
|
|
data["attendance_date"] = str(attendance_date)
|
|
data["attendance_clock_in_date"] = self.data["attendance_clock_in_date"]
|
|
data["attendance_clock_in"] = self.data["attendance_clock_in"]
|
|
data["attendance_clock_out"] = (
|
|
None
|
|
if data["attendance_clock_out"] == "None"
|
|
else data["attendance_clock_out"]
|
|
)
|
|
data["attendance_clock_out_date"] = (
|
|
None
|
|
if data["attendance_clock_out_date"] == "None"
|
|
else data["attendance_clock_out_date"]
|
|
)
|
|
data["work_type_id"] = self.data["work_type_id"]
|
|
data["shift_id"] = self.data["shift_id"]
|
|
attendance = attendances.first()
|
|
for key, value in data.items():
|
|
data[key] = str(value)
|
|
attendance.requested_data = json.dumps(data)
|
|
attendance.is_validate_request = True
|
|
if attendance.request_type != "create_request":
|
|
attendance.request_type = "update_request"
|
|
attendance.request_description = self.data["request_description"]
|
|
attendance.save()
|
|
self.new_instance = None
|
|
return
|
|
|
|
new_instance = Attendance(**data)
|
|
new_instance.is_validate_request = True
|
|
new_instance.attendance_validated = False
|
|
new_instance.request_description = self.data["request_description"]
|
|
new_instance.request_type = "create_request"
|
|
self.new_instance = new_instance
|
|
return
|
|
|
|
|
|
excluded_fields = [
|
|
"id",
|
|
"attendance_id__employee_id",
|
|
"in_datetime",
|
|
"out_datetime",
|
|
"requested_data",
|
|
"at_work_second",
|
|
"approved_overtime_second",
|
|
"is_validate_request",
|
|
"is_validate_request_approved",
|
|
"request_description",
|
|
"request_type",
|
|
"month_sequence",
|
|
"objects",
|
|
]
|
|
|
|
|
|
class AttendanceExportForm(forms.Form):
|
|
model_fields = Attendance._meta.get_fields()
|
|
field_choices = [
|
|
(field.name, field.verbose_name)
|
|
for field in model_fields
|
|
if hasattr(field, "verbose_name") and field.name not in excluded_fields
|
|
]
|
|
|
|
selected_fields = forms.MultipleChoiceField(
|
|
choices=field_choices,
|
|
widget=forms.CheckboxSelectMultiple,
|
|
initial=[
|
|
"employee_id",
|
|
"shift_id",
|
|
"work_type_id",
|
|
"attendance_date",
|
|
"attendance_clock_in",
|
|
"attendance_clock_in_date",
|
|
"attendance_clock_out",
|
|
"attendance_clock_out_date",
|
|
"attendance_worked_hour",
|
|
"attendance_validated",
|
|
],
|
|
)
|
|
|
|
|
|
class LateComeEarlyOutExportForm(forms.Form):
|
|
model_fields = AttendanceLateComeEarlyOut._meta.get_fields()
|
|
field_choices_1 = [
|
|
(field.name, field.verbose_name)
|
|
for field in model_fields
|
|
if hasattr(field, "verbose_name") and field.name not in excluded_fields
|
|
]
|
|
model_fields_2 = Attendance._meta.get_fields()
|
|
field_choices_2 = [
|
|
("attendance_id__" + field.name, field.verbose_name)
|
|
for field in model_fields_2
|
|
if hasattr(field, "verbose_name") and field.name not in excluded_fields
|
|
]
|
|
field_choices = field_choices_1 + field_choices_2
|
|
field_choices = list(OrderedDict.fromkeys(field_choices))
|
|
selected_fields = forms.MultipleChoiceField(
|
|
choices=field_choices,
|
|
widget=forms.CheckboxSelectMultiple,
|
|
initial=[
|
|
"employee_id",
|
|
"type",
|
|
"attendance_id__attendance_date",
|
|
"attendance_id__attendance_clock_in_date",
|
|
"attendance_id__attendance_clock_in",
|
|
"attendance_id__attendance_clock_out_date",
|
|
"attendance_id__attendance_clock_out",
|
|
],
|
|
)
|
|
|
|
|
|
class AttendanceActivityExportForm(forms.Form):
|
|
model_fields = AttendanceActivity._meta.get_fields()
|
|
field_choices = [
|
|
(field.name, field.verbose_name)
|
|
for field in model_fields
|
|
if hasattr(field, "verbose_name") and field.name not in excluded_fields
|
|
]
|
|
selected_fields = forms.MultipleChoiceField(
|
|
choices=field_choices,
|
|
widget=forms.CheckboxSelectMultiple,
|
|
initial=[
|
|
"employee_id",
|
|
"attendance_date",
|
|
"clock_in_date",
|
|
"clock_in",
|
|
"clock_out_date",
|
|
"clock_out",
|
|
],
|
|
)
|
|
|
|
|
|
class AttendanceOverTimeExportForm(forms.Form):
|
|
model_fields = AttendanceOverTime._meta.get_fields()
|
|
field_choices = [
|
|
(field.name, field.verbose_name)
|
|
for field in model_fields
|
|
if hasattr(field, "verbose_name") and field.name not in excluded_fields
|
|
]
|
|
selected_fields = forms.MultipleChoiceField(
|
|
choices=field_choices,
|
|
widget=forms.CheckboxSelectMultiple,
|
|
initial=[
|
|
"employee_id",
|
|
"month",
|
|
"year",
|
|
"worked_hours",
|
|
"pending_hours",
|
|
"overtime",
|
|
],
|
|
)
|
|
|
|
|
|
class GraceTimeForm(ModelForm):
|
|
"""
|
|
Form for create or update Grace time
|
|
"""
|
|
|
|
class Meta:
|
|
model = GraceTime
|
|
fields = "__all__"
|
|
widgets = {
|
|
"is_default": forms.HiddenInput(),
|
|
"allowed_time": forms.TextInput(attrs={"placeholder": "00:00:00 Hours"}),
|
|
}
|
|
|
|
exclude = ["objects", "allowed_time_in_secs", "is_active"]
|
|
|
|
|
|
class AttendanceRequestCommentForm(ModelForm):
|
|
"""
|
|
AttendanceRequestComment form
|
|
"""
|
|
|
|
class Meta:
|
|
"""
|
|
Meta class for additional options
|
|
"""
|
|
|
|
model = AttendanceRequestComment
|
|
fields = ("comment",)
|
|
|
|
|
|
def get_date_list(employee_id, from_date, to_date):
|
|
"""
|
|
This method will return a list of company working dates
|
|
"""
|
|
working_dates = get_working_days(from_date, to_date)
|
|
working_date_list = working_dates["working_days_on"]
|
|
working_date_list.sort()
|
|
attendance_dates = []
|
|
if len(working_date_list) > 0:
|
|
# filter through approved leave of employee
|
|
if apps.is_installed("leave"):
|
|
from leave.filters import LeaveRequestFilter
|
|
|
|
approved_leave_dates_filtered = LeaveRequestFilter(
|
|
data={
|
|
"from_date": working_date_list[0],
|
|
"to_date": working_date_list[-1],
|
|
"employee_id": employee_id,
|
|
"status": "approved",
|
|
}
|
|
)
|
|
approved_leave_dates_filtered = approved_leave_dates_filtered.qs
|
|
else:
|
|
approved_leave_dates_filtered = QuerySet().none()
|
|
approved_leave_dates = []
|
|
# Extract the list of approved leave dates
|
|
if len(approved_leave_dates_filtered) > 0:
|
|
for leave in approved_leave_dates_filtered:
|
|
approved_leave_dates += leave.requested_dates()
|
|
attendance_filters = AttendanceFilters(
|
|
data={
|
|
"attendance_date__gte": working_date_list[0],
|
|
"attendance_date__lte": working_date_list[-1],
|
|
"employee_id": employee_id,
|
|
}
|
|
)
|
|
existing_attendance = attendance_filters.qs
|
|
# Extract the list of attendance dates
|
|
attendance_dates = list(
|
|
existing_attendance.values_list("attendance_date", flat=True)
|
|
)
|
|
# Calculate the dates that need new attendance records
|
|
date_list = [
|
|
date
|
|
for date in working_date_list
|
|
if date not in attendance_dates and date not in approved_leave_dates
|
|
]
|
|
return date_list
|
|
|
|
|
|
class BulkAttendanceRequestForm(ModelForm):
|
|
"""
|
|
Bulk attendance request create form
|
|
"""
|
|
|
|
employee_id = forms.ModelChoiceField(
|
|
queryset=Employee.objects.filter(is_active=True),
|
|
widget=forms.Select(
|
|
attrs={
|
|
"hx-target": "#id_shift_id_div",
|
|
"hx-get": "/attendance/get-employee-shift?bulk=True",
|
|
"hx-trigger": "change",
|
|
}
|
|
),
|
|
label=_("Employee"),
|
|
)
|
|
create_bulk = forms.BooleanField(
|
|
required=False,
|
|
initial=True,
|
|
label=_("Create Bulk"),
|
|
widget=forms.CheckboxInput(
|
|
attrs={
|
|
"class": "oh-checkbox",
|
|
"hx-target": "#objectCreateModalTarget",
|
|
"hx-get": "/attendance/request-new-attendance?bulk=False",
|
|
"hx-trigger": "change",
|
|
}
|
|
),
|
|
)
|
|
|
|
from_date = forms.DateField(
|
|
required=False,
|
|
label=_("From Date"),
|
|
widget=forms.DateInput(attrs={"type": "date", "class": "form-control"}),
|
|
)
|
|
to_date = forms.DateField(
|
|
required=False,
|
|
label=_("To Date"),
|
|
widget=forms.DateInput(attrs={"type": "date", "class": "form-control"}),
|
|
)
|
|
|
|
class Meta:
|
|
"""
|
|
Meta class for additional options
|
|
"""
|
|
|
|
model = Attendance
|
|
fields = (
|
|
"employee_id",
|
|
"create_bulk",
|
|
"from_date",
|
|
"to_date",
|
|
"shift_id",
|
|
"attendance_worked_hour",
|
|
"attendance_clock_in",
|
|
"attendance_clock_out",
|
|
"minimum_hour",
|
|
"request_description",
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
request = getattr(horilla_middlewares._thread_locals, "request", None)
|
|
employee = request.user.employee_get
|
|
super().__init__(*args, **kwargs)
|
|
if employee and hasattr(employee, "employee_work_info"):
|
|
shift = employee.employee_work_info.shift_id
|
|
self.fields["shift_id"].initial = shift
|
|
|
|
def clean(self):
|
|
cleaned_data = self.cleaned_data
|
|
from_date = cleaned_data.get("from_date")
|
|
to_date = cleaned_data.get("to_date")
|
|
attendance_worked_hour = cleaned_data.get("attendance_worked_hour")
|
|
minimum_hour = cleaned_data.get("minimum_hour")
|
|
attendance_clock_out = cleaned_data.get("attendance_clock_out")
|
|
employee_id = cleaned_data.get("employee_id")
|
|
now = datetime.datetime.now().time()
|
|
today = datetime.datetime.today().date()
|
|
validate_time_format(attendance_worked_hour)
|
|
validate_time_format(minimum_hour)
|
|
attendance_date_validate(from_date)
|
|
attendance_date_validate(to_date)
|
|
date_list = get_date_list(employee_id, from_date, to_date)
|
|
if from_date and to_date and from_date > to_date:
|
|
raise ValidationError({"to_date": _("To date should be after from date")})
|
|
if to_date == today and attendance_clock_out > now:
|
|
raise ValidationError(
|
|
{
|
|
"attendance_clock_out": (
|
|
f"Check out time is in the future for the date {to_date}."
|
|
)
|
|
}
|
|
)
|
|
if employee_id and not hasattr(employee_id, "employee_work_info"):
|
|
raise ValidationError(_("Employee work info not found"))
|
|
if len(date_list) <= 0:
|
|
raise ValidationError(
|
|
_(
|
|
"There is no valid date to create attendance request between this date range"
|
|
)
|
|
)
|
|
return cleaned_data
|
|
|
|
def save(self, commit=True):
|
|
# Access cleaned data
|
|
cleaned_data = self.cleaned_data
|
|
employee_id = cleaned_data.get("employee_id")
|
|
from_date = cleaned_data.get("from_date")
|
|
to_date = cleaned_data.get("to_date")
|
|
shift_id = cleaned_data.get("shift_id")
|
|
attendance_clock_in = cleaned_data.get("attendance_clock_in")
|
|
attendance_clock_out = cleaned_data.get("attendance_clock_out")
|
|
request_description = cleaned_data.get("request_description")
|
|
attendance_worked_hour = cleaned_data.get("attendance_worked_hour")
|
|
minimum_hour = cleaned_data.get("minimum_hour")
|
|
work_type_id = employee_id.employee_work_info.work_type_id
|
|
date_list = get_date_list(employee_id, from_date, to_date)
|
|
# Prepare initial data for the form
|
|
initial_data = {
|
|
"employee_id": employee_id,
|
|
"shift_id": shift_id,
|
|
"work_type_id": work_type_id,
|
|
"attendance_clock_in": attendance_clock_in,
|
|
"attendance_clock_out": attendance_clock_out,
|
|
"attendance_worked_hour": attendance_worked_hour,
|
|
"is_validate_request": True,
|
|
"minimum_hour": minimum_hour,
|
|
"request_description": request_description,
|
|
}
|
|
# Iterate over the dates and create attendance requests
|
|
for date in date_list:
|
|
initial_data.update(
|
|
{
|
|
"attendance_date": date,
|
|
"attendance_clock_in_date": date,
|
|
"attendance_clock_out_date": date,
|
|
"attendance_clock_in_date": date,
|
|
}
|
|
)
|
|
form = NewRequestForm(data=initial_data)
|
|
if form.is_valid():
|
|
instance = form.save(commit=False)
|
|
instance.is_validate_request = True
|
|
instance.employee_id = employee_id
|
|
instance.request_type = "create_request"
|
|
instance.is_bulk_request = True
|
|
instance.save()
|
|
else:
|
|
logger(form.errors)
|
|
instance = super().save(commit=False)
|
|
if commit:
|
|
instance.save()
|
|
|
|
return instance
|
|
|
|
|
|
class WorkRecordsForm(ModelForm):
|
|
"""
|
|
WorkRecordForm
|
|
"""
|
|
|
|
class Meta:
|
|
"""
|
|
Meta class for additional options
|
|
"""
|
|
|
|
fields = "__all__"
|
|
model = WorkRecords
|