diff --git a/.dockerignore b/.dockerignore
index 4b8641f80..7b74c129b 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -2,4 +2,4 @@
.gitignore
*.md
LICENSE
-docker-compose.yaml
\ No newline at end of file
+docker-compose.yaml
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index f04225162..56d72a6fe 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -5,4 +5,4 @@ contact_links:
about: Ask questions or discuss features here
- name: Documentation
url: https://horilla-opensource.github.io/horilla-docs/
- about: Check the official Horilla documentation
\ No newline at end of file
+ about: Check the official Horilla documentation
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 1921e67bd..683882644 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -117,4 +117,4 @@ body:
- label: I have searched for duplicate feature requests
required: true
- label: I have provided clear and concise information
- required: true
\ No newline at end of file
+ required: true
diff --git a/asset/cbv/accessibility.py b/asset/cbv/accessibility.py
new file mode 100644
index 000000000..b141027bf
--- /dev/null
+++ b/asset/cbv/accessibility.py
@@ -0,0 +1,25 @@
+
+from django.contrib.auth.context_processors import PermWrapper
+from base.methods import check_manager
+from employee.models import Employee
+
+
+def asset_accessibility(request, instance: object = None, user_perms: PermWrapper = [], *args, **kwargs) -> bool:
+ """
+ accessibility for asset tab
+ """
+ employee = Employee.objects.get(id=instance.pk)
+ if (
+ request.user.has_perm("asset.view_asset") or check_manager(request.user.employee_get, instance)
+ or request.user == employee.employee_user_id
+ ):
+ return True
+ return False
+
+
+def create_asset_request_accessibility(
+ request, instance: object = None, user_perms: PermWrapper = [], *args, **kwargs
+) -> bool:
+ if request.user.has_perm("asset.add_assetrequest"):
+ return True
+ return False
\ No newline at end of file
diff --git a/asset/cbv/asset_batch_no.py b/asset/cbv/asset_batch_no.py
new file mode 100644
index 000000000..8b8fc6943
--- /dev/null
+++ b/asset/cbv/asset_batch_no.py
@@ -0,0 +1,189 @@
+"""
+this page is handling the cbv methods for asset batch no
+"""
+
+from typing import Any
+from django.http import HttpResponse
+from django.urls import reverse
+from django.contrib import messages
+from django.utils.decorators import method_decorator
+from django.utils.translation import gettext_lazy as _
+from asset.filters import AssetBatchNoFilter
+from asset.forms import AssetBatchForm
+from asset.models import AssetLot
+from horilla_views.cbv_methods import login_required, permission_required
+from horilla_views.generic.cbv.views import (
+ HorillaDetailedView,
+ HorillaFormView,
+ HorillaListView,
+ HorillaNavView,
+ TemplateView,
+)
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.view_assetlot"), name="dispatch")
+class AssetBatchNoView(TemplateView):
+ """
+ for Asset batch no page
+ """
+
+ template_name = "cbv/asset_batch_no/asset_batch_no.html"
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.view_assetlot"), name="dispatch")
+class AssetBatchNoListView(HorillaListView):
+ """
+ list view for batch number
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("asset-batch-list")
+ self.view_id = "AssetBatchList"
+
+ model = AssetLot
+ filter_class = AssetBatchNoFilter
+
+ columns = [
+ (_("Batch Number"), "lot_number"),
+ (_("Description"), "lot_description"),
+ (_("Assets"),"assets_column")
+ ]
+
+
+ header_attrs = {
+ "action" : """
+ style = "width:180px !important"
+ """
+ }
+
+ action_method = "actions"
+
+ row_attrs = """
+ hx-get='{asset_batch_detail}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.view_assetlot"), name="dispatch")
+class AssetBatchNoNav(HorillaNavView):
+ """
+ Nav bar
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("asset-batch-list")
+
+ if self.request.user.has_perm("asset.view_assetlot"):
+ self.create_attrs = f"""
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ hx-target="#genericModalBody"
+ hx-get="{reverse('asset-batch-number-creation')}"
+ """
+
+ nav_title = _("Asset Batch Number")
+ filter_instance = AssetBatchNoFilter()
+ search_swap_target = "#listContainer"
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.add_assetlot"), name="dispatch")
+class AssetBatchCreateFormView(HorillaFormView):
+ """
+ form view for create batch number
+ """
+
+ form_class = AssetBatchForm
+ model = AssetLot
+ new_display_title = _("Create Batch Number")
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ if self.form.instance.pk:
+ self.form_class.verbose_name = _("Batch Number Update")
+
+ return context
+
+ def form_valid(self, form: AssetBatchForm) -> HttpResponse:
+ if form.is_valid():
+ if form.instance.pk:
+ message = _("Batch number updated successfully.")
+ else:
+ message = _("Batch number created successfully.")
+ form.save()
+ messages.success(self.request, _(message))
+ return self.HttpResponse()
+ return super().form_valid(form)
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.add_assetlot"), name="dispatch")
+class DynamicCreateBatchNo(AssetBatchCreateFormView):
+ """
+ view for dynamic batch create
+ """
+
+ is_dynamic_create_view = True
+
+
+class AssetBatchDetailView(HorillaDetailedView):
+ """
+ detail view of the page
+ """
+
+ def get_context_data(self, **kwargs: Any):
+ """
+ Return context data with the title set to the contract's name.
+ """
+
+ context = super().get_context_data(**kwargs)
+ lot_number = context["assetlot"].lot_number
+ context["title"] = "Asset Batch:" + lot_number
+ return context
+
+ model = AssetLot
+ header = False
+
+ cols = {
+ "assets_column":12,
+ "lot_description":12,
+ "lot_number":12
+ }
+ body = {
+ (_("Assets"),"assets_column"),
+ (_("Description"), "lot_description"),
+ (_("Batch Number"), "lot_number"),
+
+ }
+
+ actions = [
+ {
+ "action": _("Edit"),
+ "icon": "create-outline",
+ "attrs": """
+ class="oh-btn oh-btn--info w-100"
+ hx-get='{get_update_url}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ """,
+ },
+ {
+ "action": _("Delete"),
+ "icon": "trash-outline",
+
+ "attrs": """
+ class="oh-btn oh-btn--danger w-100"
+ hx-confirm="Do you want to delete this batch number?"
+ hx-post="{get_delete_url}?instance_ids={ordered_ids}"
+ hx-target="#AssetBatchList"
+ """
+ },
+ ]
diff --git a/asset/cbv/asset_category.py b/asset/cbv/asset_category.py
new file mode 100644
index 000000000..7aa58db28
--- /dev/null
+++ b/asset/cbv/asset_category.py
@@ -0,0 +1,309 @@
+"""
+Asset category forms
+"""
+
+from typing import Any
+from django import forms
+from django.contrib import messages
+from django.http import HttpResponse
+from django.urls import reverse
+from django.utils.decorators import method_decorator
+from django.utils.translation import gettext_lazy as _
+from asset.cbv.asset_batch_no import DynamicCreateBatchNo
+from asset.filters import AssetCategoryFilter, AssetFilter, CustomAssetFilter
+from asset.forms import AssetCategoryForm, AssetForm, AssetReportForm
+from asset.models import Asset, AssetCategory, AssetDocuments, AssetReport
+from horilla_views.generic.cbv.views import (
+ HorillaDetailedView,
+ HorillaFormView,
+ HorillaNavView,
+)
+from horilla_views.cbv_methods import login_required, permission_required
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.view_assetcategory"), name="dispatch")
+class AssetCategoryFormView(HorillaFormView):
+ """
+ form view for create asset category
+ """
+
+ form_class = AssetCategoryForm
+ model = AssetCategory
+ new_display_title = _("Asset Category Creation")
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ if self.form.instance.pk:
+ self.form_class.verbose_name = _("Asset Category Update")
+
+ return context
+
+ def form_valid(self, form: AssetCategoryForm) -> HttpResponse:
+ if form.is_valid():
+ if form.instance.pk:
+ message = _("Asset category updated successfully")
+ else:
+ message = _("Asset category created successfully")
+ form.save()
+ messages.success(self.request, _(message))
+ return HttpResponse(
+ ""
+ )
+ return super().form_valid(form)
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.add_asset"), name="dispatch")
+class AssetFormView(HorillaFormView):
+ """
+ form view for create asset
+ """
+
+ form_class = AssetForm
+ model = Asset
+ new_display_title = _("Asset Creation")
+ dynamic_create_fields = [("asset_lot_number_id", DynamicCreateBatchNo)]
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ asset_category_id = self.kwargs.get("asset_category_id")
+ self.form.fields["asset_category_id"].initial = asset_category_id
+ self.form.fields["asset_category_id"].widget = forms.HiddenInput()
+ if self.form.instance.pk:
+ self.form_class.verbose_name = _("Asset Update")
+ return context
+
+ def form_valid(self, form: AssetForm) -> HttpResponse:
+ if form.is_valid():
+ if form.instance.pk:
+ message = _("Asset updated successfully")
+ else:
+ message = _("Asset created successfully")
+ form.save()
+ messages.success(self.request, _(message))
+ return HttpResponse(
+ ""
+ )
+ return super().form_valid(form)
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.view_assetcategory"), name="dispatch")
+class AssetCategoryDuplicateFormView(HorillaFormView):
+ """
+ form view for create duplicate asset category
+ """
+
+ form_class = AssetCategoryForm
+ model = AssetCategory
+ new_display_title = _("Asset Category Duplicate")
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ original_object = AssetCategory.objects.get(id=self.kwargs["obj_id"])
+ form = self.form_class(instance=original_object)
+ for field_name, field in form.fields.items():
+ if isinstance(field, forms.CharField):
+ if field.initial:
+ initial_value = field.initial
+ else:
+ initial_value = f"{form.initial.get(field_name, '')} (copy)"
+ form.initial[field_name] = initial_value
+ form.fields[field_name].initial = initial_value
+ context["form"] = form
+ self.form_class.verbose_name = _("Duplicate")
+ return context
+
+ def form_valid(self, form: AssetCategoryForm) -> HttpResponse:
+ if form.is_valid():
+ message = _("Asset category created successfully")
+ form.save()
+ messages.success(self.request, _(message))
+ return HttpResponse(
+ ""
+ )
+ return super().form_valid(form)
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.view_asset"), name="dispatch")
+class AssetDuplicateFormView(HorillaFormView):
+ """
+ form view for create duplicate for asset
+ """
+
+ form_class = AssetForm
+ model = Asset
+ new_display_title = _("Asset Duplicate")
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ original_object = Asset.objects.get(id=self.kwargs["obj_id"])
+ form = self.form_class(instance=original_object)
+ form.fields["asset_category_id"].widget = forms.HiddenInput()
+ for field_name, field in form.fields.items():
+ if isinstance(field, forms.CharField):
+ if field.initial:
+ initial_value = field.initial
+ else:
+ initial_value = f"{form.initial.get(field_name, '')} (copy)"
+ form.initial[field_name] = initial_value
+ form.fields[field_name].initial = initial_value
+ context["form"] = form
+ self.form_class.verbose_name = _("Duplicate")
+ return context
+
+ def form_valid(self, form: AssetForm) -> HttpResponse:
+ if form.is_valid():
+ if form.instance.pk:
+ message = _("Asset updated successfully")
+ else:
+ message = _("Asset created successfully")
+ form.save()
+ messages.success(self.request, _(message))
+ return HttpResponse(
+ ""
+ )
+ return super().form_valid(form)
+
+class AssetReportFormView(HorillaFormView):
+ """
+ form view for create button
+ """
+
+ form_class = AssetReportForm
+ model = AssetReport
+ new_display_title = _("Add Asset Report")
+
+ def get_initial(self) -> dict:
+ initial = super().get_initial()
+ initial["asset_id"] = self.kwargs.get("asset_id")
+ return initial
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ asset_id = self.kwargs.get("asset_id")
+ asset = Asset.objects.filter(id=asset_id)
+ self.form.fields["asset_id"].queryset = asset
+ return context
+
+ def form_valid(self, form: AssetReportForm) -> HttpResponse:
+ if form.is_valid():
+ message = _("Asset report added successfully.")
+ asset = form.save()
+ uploaded_files = form.cleaned_data.get('files')
+ if uploaded_files:
+ for file in uploaded_files:
+ AssetDocuments.objects.create(asset_report=asset, file=file)
+ messages.success(self.request, message)
+ return self.HttpResponse()
+ return super().form_valid(form)
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.view_asset"), name="dispatch")
+class AssetCategoryNav(HorillaNavView):
+ """
+ nav bar
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("asset-category-view-search-filter")
+ self.create_attrs = f"""
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ hx-get="{reverse('asset-category-creation')}"
+ hx-target="#genericModalBody"
+ """
+ # if self.request.user.has_perm(
+ # "attendance.add_attendanceovertime"
+ # ) or is_reportingmanager(self.request):
+
+ # self.actions = [
+ # {
+ # "action": _("Import"),
+ # "attrs": """
+ # onclick="
+ # reqAttendanceBulkApprove();
+ # "
+ # style="cursor: pointer;"
+ # """,
+ # },
+ # {
+ # "action": _("Export"),
+ # "attrs": """
+ # onclick="reqAttendanceBulkReject();"
+ # style="color:red !important"
+ # """,
+ # },
+ # ]
+ # else:
+ # self.actions = None
+
+ nav_title = _("Asset Category")
+ filter_body_template = "cbv/asset_category/filter.html"
+ filter_instance = AssetFilter()
+ filter_form_context_name = "form"
+ search_swap_target = "#assetCategoryList"
+
+
+
+
+class AssetCategoryDetailView(HorillaDetailedView):
+ """
+ Detail view of the page
+ """
+
+
+
+ def get_context_data(self, **kwargs: Any):
+ """
+ Return context data with the title set to the contract's name.
+ """
+
+ context = super().get_context_data(**kwargs)
+ asset_name = context["asset"].asset_name
+ context["title"] = asset_name
+ return context
+
+ model = Asset
+ header = False
+ template_name = "cbv/asset_category/detail_view_action.html"
+ body = [
+ (_("Tracking Id"), "asset_tracking_id"),
+ (_("Purchase Date"), "asset_purchase_date"),
+ (_("Cost"), "asset_purchase_cost"),
+ (_("Status"), "get_status_display"),
+ (_("Batch No"), "asset_lot_number_id__lot_number"),
+ (_("Category"), "asset_category_id"),
+ ]
+
+ actions = [
+ {
+ "action": _("Edit"),
+ "icon": "create-outline",
+ "attrs": """
+ class="oh-btn oh-btn--info w-100"
+ hx-get='{get_update_url}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ """,
+ },
+ {
+ "action": _("Delete"),
+ "icon": "trash-outline",
+
+ "attrs": """
+ class="oh-btn oh-btn--danger w-100"
+ hx-confirm="Do you want to delete this asset?"
+ hx-post="{get_delete_url}?instance_ids={ordered_ids}"
+ hx-target="#genericModalBody"
+ """
+ },
+ ]
+
+
+
diff --git a/asset/cbv/asset_history.py b/asset/cbv/asset_history.py
new file mode 100644
index 000000000..fb606eef0
--- /dev/null
+++ b/asset/cbv/asset_history.py
@@ -0,0 +1,119 @@
+"""
+this page is handling the cbv methods of asset history page
+"""
+
+from typing import Any
+from django.urls import reverse
+from django.utils.decorators import method_decorator
+from django.utils.translation import gettext_lazy as _
+from asset.filters import AssetHistoryFilter
+from asset.models import AssetAssignment
+from horilla_views.generic.cbv.views import (
+ HorillaDetailedView,
+ TemplateView,
+ HorillaListView,
+ HorillaNavView,
+)
+from horilla_views.cbv_methods import login_required, permission_required
+
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.view_assetassignment"), name="dispatch")
+class AssetHistoryView(TemplateView):
+ """
+ for page view
+ """
+
+ template_name = "cbv/asset_history/asset_history_home.html"
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.view_assetassignment"), name="dispatch")
+class AssetHistorylistView(HorillaListView):
+ """
+ list view
+ """
+
+ filter_class = AssetHistoryFilter
+ model = AssetAssignment
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("asset-history-list")
+
+ columns = [
+ (_("Asset"),"asset_id__asset_name", "get_avatar"),
+ (_("Employee"),"assigned_to_employee_id"),
+ (_("Assigned Date"),"assigned_date"),
+ (_("Returned Date"),"return_date"),
+ (_("Return Status"),"return_status")
+ ]
+
+ records_per_page = 5
+
+ sortby_mapping = [
+ ("Asset","asset_id__asset_name", "get_avatar"),
+ ("Employee","assigned_to_employee_id"),
+ ("Assigned Date","assigned_date"),
+ ("Returned Date","return_date"),
+ ]
+
+ row_attrs = """
+ hx-get='{asset_detail_view}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.view_assetassignment"), name="dispatch")
+class AssetHistoryNavView(HorillaNavView):
+ """
+ navbar
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("asset-history-list")
+
+ nav_title = _("Asset History")
+ filter_body_template = "cbv/asset_history/asset_history_filter.html"
+ filter_form_context_name = "form"
+ filter_instance = AssetHistoryFilter()
+ search_swap_target = "#listContainer"
+
+ group_by_fields = [
+ ("asset_id__asset_name",_("Asset")),
+ ("assigned_to_employee_id",_("Employee")),
+ ("assigned_date",_("Assigned Date")),
+ ("return_date",_("Returned Date")),
+
+ ]
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required("asset.view_assetassignment"), name="dispatch")
+class AssetHistoryDetailView(HorillaDetailedView):
+ """
+ detail view of the page
+ """
+
+ model = AssetAssignment
+ title = _("Asset Details")
+ header = {
+ "title":"asset_id",
+ "subtitle":"asset_id__asset_category_id",
+ "avatar":"assigned_to_employee_id__get_avatar"
+ }
+ body = [
+ (_("Allocated User"),"assigned_to_employee_id"),
+ (_("Returned Status"),"return_status"),
+ (_("Allocated Date"),"assigned_date"),
+ (_("Returned Date"),"return_date"),
+ (_("Asset"),"asset_id"),
+ (_("Return Description"),"return_condition"),
+ (_("Assign Condition Images"),"assign_condition_img",True),
+ (_("Return Condition Images"),"return_condition_img",True)
+ ]
diff --git a/asset/cbv/asset_tab.py b/asset/cbv/asset_tab.py
new file mode 100644
index 000000000..0f17821ed
--- /dev/null
+++ b/asset/cbv/asset_tab.py
@@ -0,0 +1,119 @@
+"""
+This page is handling the cbv methods of asset tab in profile page.
+"""
+
+from typing import Any
+from django.utils.translation import gettext_lazy as _
+from django.urls import reverse
+from asset.cbv.request_and_allocation import AllocationList, AssetRequestList
+from employee.models import Employee
+from horilla_views.generic.cbv.views import HorillaTabView
+from employee.cbv.employee_profile import EmployeeProfileView
+
+
+class AssetTabListView(AllocationList):
+ """
+ Asset tab in individual view
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ pk = self.request.resolver_match.kwargs.get('pk')
+ self.search_url = reverse("assets-tab-list-view",kwargs= {'pk': pk} )
+ self.view_id = "asset-div"
+
+
+
+ columns = AllocationList.columns + [
+ (_("Status"), "status_display"),
+ (_("Assigned Date"), "assigned_date_display"),
+ ]
+
+ def get_queryset(self):
+ """
+ Returns a filtered queryset of records assigned to a specific employee
+ """
+
+ queryset = super().get_queryset()
+ pk = self.kwargs.get("pk")
+ queryset = queryset.filter(assigned_to_employee_id=pk).exclude(
+ return_status__isnull=False
+ )
+ return queryset
+
+
+class AssetRequestTab(AssetRequestList):
+ """
+ Asset request tab
+ """
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ pk = self.request.resolver_match.kwargs.get('pk')
+ self.search_url = reverse("asset-request-tab-list-view",kwargs= {'pk': pk} )
+ self.view_id = "asset-request-div"
+
+ def get_queryset(self):
+ """
+ Returns a filtered queryset of records for the requested employee.
+ """
+
+ queryset = super().get_queryset()
+ pk = self.kwargs.get("pk")
+ queryset = queryset.filter(requested_employee_id=pk)
+ return queryset
+
+
+class AssetTabView(HorillaTabView):
+ """
+ generic tab view for asset tab
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.view_id = "asset-tab"
+
+ def get_context_data(self, **kwargs):
+ """
+ Adds the employee details and tab information to the context.
+ """
+
+ context = super().get_context_data(**kwargs)
+ pk = self.kwargs.get("pk")
+ context["emp_id"] = pk
+ employee = Employee.objects.get(id=pk)
+ context["instance"] = employee
+ context["tabs"] = [
+ {
+ "title": _("Assets"),
+ "url": f"{reverse('assets-tab-list-view',kwargs={'pk': pk})}",
+ },
+ {
+ "title": _("Asset Request"),
+ "url": f"{reverse('asset-request-tab-list-view',kwargs={'pk': pk})}",
+ "actions": [
+ {
+ "action": "Create Request",
+ "accessibility" :"asset.cbv.accessibility.create_asset_request_accessibility",
+ "attrs": f"""
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ hx-get="{reverse('asset-request-creation')}?pk={pk}"
+ hx-target="#genericModalBody"
+ style="cursor: pointer;"
+ """,
+ }
+ ],
+ },
+ ]
+ return context
+
+
+EmployeeProfileView.add_tab(
+ tabs=[
+ {
+ "title": "Asset",
+ "view": AssetTabView.as_view(),
+ "accessibility": "asset.cbv.accessibility.asset_accessibility",
+ },
+ ]
+)
diff --git a/asset/cbv/dashboard.py b/asset/cbv/dashboard.py
new file mode 100644
index 000000000..2bbb78ede
--- /dev/null
+++ b/asset/cbv/dashboard.py
@@ -0,0 +1,78 @@
+from typing import Any
+from django.urls import reverse
+from django.utils.decorators import method_decorator
+from asset.cbv.request_and_allocation import AssetAllocationList, AssetRequestList
+from base.methods import filtersubordinates
+from horilla_views.generic.cbv.views import HorillaListView
+from horilla_views.cbv_methods import login_required, permission_required
+
+
+@method_decorator(login_required,name="dispatch")
+class AssetRequestToApprove(AssetRequestList):
+ """
+ Asset request to approve in dashboard
+ """
+
+ columns = [
+ column for column in AssetRequestList.columns if column[1] != "status_col"
+ ]
+
+ bulk_select_option = False
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("dashboard-asset-request-approve")
+
+ def get_queryset(self):
+ queryset = HorillaListView.get_queryset(self)
+ queryset = queryset.filter(
+ asset_request_status="Requested", requested_employee_id__is_active=True
+ )
+ queryset = filtersubordinates(
+ self.request,
+ queryset,
+ "asset.change_assetrequest",
+ field="requested_employee_id",
+ )
+ return queryset
+
+ header_attrs = {
+ "requested_employee_id": """
+ style ="width:100px !important"
+ """,
+ "asset_category_id": """
+ style ="width:100px !important"
+ """,
+ "asset_request_date": """
+ style ="width:100px !important"
+ """,
+ "status_col": """
+ style ="width:100px !important"
+ """,
+ "action": """
+ style ="width:100px !important"
+ """
+ }
+
+@method_decorator(login_required,name="dispatch")
+@method_decorator(permission_required("asset.view_assetcategory"),name="dispatch")
+class AllocatedAssetsList(AssetAllocationList):
+ """
+ List of allocated assets in dashboard
+ """
+
+ columns = [
+ column
+ for column in AssetAllocationList.columns
+ if column[1] != "return_status_col"
+ ]
+ bulk_select_option = False
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ queryset = queryset.filter(
+ asset_id__asset_status="In use", assigned_to_employee_id__is_active=True
+ )
+ return queryset
+
+ action_method = None
diff --git a/asset/cbv/request_and_allocation.py b/asset/cbv/request_and_allocation.py
new file mode 100644
index 000000000..d56572559
--- /dev/null
+++ b/asset/cbv/request_and_allocation.py
@@ -0,0 +1,544 @@
+"""
+Request and allocation page
+"""
+
+from typing import Any
+from django.http import HttpResponse
+from django.shortcuts import render
+from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
+from django.utils.decorators import method_decorator
+from django.contrib import messages
+from asset.filters import AssetAllocationFilter, AssetRequestFilter, CustomAssetFilter
+from asset.forms import AssetAllocationForm, AssetRequestForm
+from asset.models import Asset, AssetAssignment, AssetRequest, ReturnImages
+from base.methods import filtersubordinates
+from employee.models import Employee
+from notifications.signals import notify
+from horilla.horilla_middlewares import _thread_locals
+from horilla_views.cbv_methods import login_required, permission_required
+from horilla_views.generic.cbv.views import (
+ HorillaDetailedView,
+ HorillaFormView,
+ HorillaListView,
+ HorillaNavView,
+ HorillaTabView,
+ TemplateView,
+)
+
+
+@method_decorator(login_required, name="dispatch")
+class RequestAndAllocationView(TemplateView):
+ """
+ for request and allocation page
+ """
+
+ template_name = "cbv/request_and_allocation/request_and_allocation.html"
+
+
+@method_decorator(login_required, name="dispatch")
+class AllocationList(HorillaListView):
+ """
+ For both asset allocation and asset tab
+ """
+
+ # view_id = "view-container"
+
+ bulk_update_fields = [
+ "asset_id__expiry_date"
+ ]
+
+ model = AssetAssignment
+ filter_class = AssetAllocationFilter
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("list-asset")
+
+ columns = [
+ (_("Asset"), "asset_id__asset_name", "get_avatar"),
+ (_("Category"), "asset_id__asset_category_id"),
+ (_("Expiry Date"), "asset_id__expiry_date"),
+ ]
+
+
+ header_attrs = {
+ "action" : """
+ style = "width:180px !important"
+ """,
+ "asset_id__asset_name" :"""
+ style = "width:250px !important"
+ """,
+ "asset_id__asset_category_id" : """
+ style = "width:250px !important"
+ """,
+ "asset_id__expiry_date" : """
+ style = "width:250px !important"
+ """,
+ }
+
+ sortby_mapping = [
+ ("Category", "asset_id__asset_category_id__asset_category_name"),
+ ("Expiry Date", "asset_id__expiry_date")
+ ]
+
+ action_method = "asset_action"
+
+ row_attrs = """
+ hx-get='{detail_view_asset}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+
+
+@method_decorator(login_required, name="dispatch")
+class AssetList(AllocationList):
+ """
+ Asset tab
+ """
+
+ # view_id = "assetlist"
+ def get_queryset(self):
+ """
+ Returns a queryset of AssetRequest objects filtered by
+ the current user's employee ID.
+ """
+ queryset = super().get_queryset()
+ employee = self.request.user.employee_get
+ queryset = queryset.filter(assigned_to_employee_id=employee).exclude(
+ return_status__isnull=False
+ )
+ return queryset
+
+ selected_instances_key_id = "assetlistInstances"
+
+
+
+
+@method_decorator(login_required, name="dispatch")
+class AssetAllocationList(AllocationList):
+ """
+ Asset allocation tab
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("list-asset-allocation")
+
+ columns = [
+ (
+ _("Allocated User"),
+ "assigned_to_employee_id",
+ "assigned_to_employee_id__get_avatar",
+ ),
+ (_("Asset"), "asset_id"),
+ (_("Assigned Date"), "assigned_date"),
+ (_("Return Date"), "return_status_col"),
+ ]
+
+ sortby_mapping = [
+ ("Allocated User","assigned_to_employee_id__get_full_name"),
+ ("Asset", "asset_id__asset_name"),
+ ("Assigned Date", "assigned_date"),
+ ("Return Date", "return_status_col"),
+ ]
+
+ action_method = "allocation_action"
+
+ row_attrs = """
+ hx-get='{detail_view_asset_allocation}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+
+
+@method_decorator(login_required, name="dispatch")
+class AssetRequestList(HorillaListView):
+ """
+ Asset Request Tab
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+
+ self.search_url = reverse("list-asset-request")
+ # self.view_id = "view-container"
+ if self.request.user.has_perm("asset.add_assetassignment"):
+ self.action_method = "action_col"
+
+ model = AssetRequest
+ filter_class = AssetRequestFilter
+
+ def get_queryset(self):
+ """
+ Returns a filtered queryset of AssetRequest objects
+ based on user permissions and employee ID.
+ """
+
+ queryset = super().get_queryset()
+ queryset = filtersubordinates(
+ request=self.request,
+ perm="asset.view_assetrequest",
+ queryset=queryset,
+ field="requested_employee_id",
+ ) | queryset.filter(requested_employee_id=self.request.user.employee_get)
+ return queryset
+
+ columns = [
+ (
+ _("Request User"),
+ "requested_employee_id",
+ "requested_employee_id__get_avatar",
+ ),
+ (_("Asset Category"), "asset_category_id"),
+ (_("Requested Date"), "asset_request_date"),
+ (_("Status"), "status_col"),
+ ]
+
+ header_attrs = {
+ "action" : """
+ style = "width:180px !important"
+ """
+ }
+
+ sortby_mapping = [
+ ("Request User","requested_employee_id__get_full_name"),
+ ("Asset Category", "asset_category_id__asset_category_name"),
+ ("Requested Date", "asset_request_date"),
+ ("Status", "status_col"),
+ ]
+
+ row_attrs = """
+ hx-get='{detail_view_asset_request}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+
+
+@method_decorator(login_required, name="dispatch")
+class RequestAndAllocationTab(HorillaTabView):
+ """
+ Tab View
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.tabs = [
+ {
+ "title": _("Asset"),
+ "url": f"{reverse('list-asset')}",
+ },
+ {
+ "title": _("Asset Request"),
+ "url": f"{reverse('list-asset-request')}",
+ "actions": [
+ {
+ "action": "Create Request",
+ "attrs": f"""
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ hx-get="{reverse('asset-request-creation')}"
+ hx-target="#genericModalBody"
+ style="cursor: pointer;"
+ """,
+ }
+ ],
+ },
+ ]
+ if self.request.user.has_perm("asset.view_assetassignment"):
+ self.tabs.append(
+ {
+ "title": _("Asset Allocation"),
+ "url": f"{reverse('list-asset-allocation')}",
+ "actions": [
+ {
+ "action": "Create Allocation",
+ "attrs": f"""
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ hx-get="{reverse('asset-allocate-creation')}"
+ hx-target="#genericModalBody"
+ style="cursor: pointer;"
+ """,
+ }
+ ],
+ },
+ )
+
+
+@method_decorator(login_required, name="dispatch")
+class RequestAndAllocationNav(HorillaNavView):
+ """
+ Nav bar
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("tab-asset-request-allocation")
+
+ nav_title = _("Asset")
+ filter_instance = AssetAllocationFilter()
+ filter_form_context_name = "asset_allocation_filter_form"
+ filter_body_template = "cbv/request_and_allocation/filter.html"
+ search_swap_target = "#listContainer"
+
+ def get_context_data(self, **kwargs):
+ """
+ context data
+ """
+ context = super().get_context_data(**kwargs)
+ assets_filter_form = CustomAssetFilter()
+ asset_request_filter_form = AssetRequestFilter()
+ context["assets_filter_form"] = assets_filter_form.form
+ context["asset_request_filter_form"] = asset_request_filter_form.form
+ return context
+
+ group_by_fields = [
+ ("requested_employee_id", _("Asset Request / Employee")),
+ ("asset_category_id", _("Asset Request / Asset Category")),
+ ("asset_request_date", _("Asset Request / Request Date")),
+ ("asset_request_status", _("Asset Request / Status")),
+ ("assigned_to_employee_id", _("Asset Allocation / Employee")),
+ ("assigned_date", _("Asset Allocation / Assigned Date")),
+ ("return_date", _("Asset Allocation / Return Date")),
+ ]
+
+
+@method_decorator(login_required, name="dispatch")
+class AssetDetailView(HorillaDetailedView):
+ """
+ detail view of asset tab
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.body = [
+ (_("Description"), "asset_id__asset_description"),
+ (_("Tracking Id"), "asset_id__asset_tracking_id"),
+ (_("Assigned Date"), "assigned_date"),
+ (_("Status"), "asset_detail_status"),
+ (_("Assigned by"), "assigned_by_employee_id"),
+ (_("Batch No"), "asset_id__asset_lot_number_id"),
+ # ("Category","asset_id__asset_category_id")
+ ]
+
+ action_method = "asset_detail_action"
+
+ model = AssetAssignment
+ title = _("Asset Information")
+ header = {
+ "title": "asset_id__asset_name",
+ "subtitle": "asset_id__asset_category_id",
+ "avatar": "get_avatar",
+ }
+
+
+@method_decorator(login_required, name="dispatch")
+class AssetRequestDetailView(HorillaDetailedView):
+ """
+ detail view of asset request tab
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.body = [
+ (_("Asset Category"), "asset_category_id"),
+ (_("Requested Date"), "asset_request_date"),
+ (_("Request Description"), "description"),
+ (_("Status"), "status_col"),
+ ]
+
+ model = AssetRequest
+ title = _("Details")
+ header = {
+ "title": "requested_employee_id",
+ "subtitle": "asset_request_detail_subtitle",
+ "avatar": "requested_employee_id__get_avatar",
+ }
+ action_method = "detail_action_col"
+
+
+@method_decorator(login_required, name="dispatch")
+class AssetAllocationDetailView(HorillaDetailedView):
+ """
+ detail view of asset allocation tab
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.body = [
+ (_("Returned Status"), "return_status"),
+ (_("Allocated User"), "assigned_by_employee_id"),
+ (_("Allocated Date"), "assigned_date"),
+ (_("Return Date"), "return_date"),
+ (_("Asset"), "asset_id"),
+ (_("Return Description"), "return_condition"),
+ (_("Status"), "detail_status"),
+ ]
+
+ model = AssetAssignment
+ title = _("Details")
+ header = {
+ "title": "assigned_to_employee_id",
+ "subtitle": "asset_allocation_detail_subtitle",
+ "avatar": "assigned_to_employee_id__get_avatar",
+ }
+ action_method = "asset_allocation_detail_action"
+
+
+@method_decorator(login_required, name="dispatch")
+class AssetRequestCreateForm(HorillaFormView):
+ """
+ Create Asset request
+ """
+
+ model = AssetRequest
+ form_class = AssetRequestForm
+ template_name = "cbv/request_and_allocation/forms/req_form.html"
+ new_display_title = _("Asset Request")
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ if self.request.GET.get('pk'):
+ pk = self.request.GET.get('pk')
+ self.form.fields["requested_employee_id"].queryset = Employee.objects.filter(
+ id=pk
+ )
+ self.form.fields["requested_employee_id"].initial = pk
+ return context
+
+ def form_valid(self, form: AssetRequestForm) -> HttpResponse:
+ """
+ Handles validation and saving of an AssetRequestForm.
+ """
+ if form.is_valid():
+ message = _("Asset Request Created Successfully")
+ form.save()
+ messages.success(self.request, message)
+ return self.HttpResponse()
+ return super().form_valid(form)
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required(perm="asset.add_asset"),name="dispatch")
+class AssetAllocationFormView(HorillaFormView):
+ """
+ Create Asset Allocation
+ """
+
+ model = AssetAssignment
+ form_class = AssetAllocationForm
+ template_name = "cbv/request_and_allocation/forms/allo_form.html"
+ new_display_title = _("Asset Allocation")
+
+
+
+ def form_valid(self, form: AssetAllocationForm) -> HttpResponse:
+ """
+ form valid function
+ """
+ if form.is_valid():
+ asset = form.instance.asset_id
+ asset.asset_status = "In use"
+ asset.save()
+ message = _("Asset allocated Successfully")
+ form.save()
+ request = getattr(_thread_locals, "request", None)
+ files = request.FILES.getlist("asset_condition_img")
+ attachments = []
+ if request.FILES:
+ for file in files:
+ attachment = ReturnImages()
+ attachment.image = file
+ attachment.save()
+ attachments.append(attachment)
+ form.instance.assign_images.add(*attachments)
+ messages.success(self.request, message)
+ return self.HttpResponse()
+ return super().form_valid(form)
+
+
+class AssetApproveFormView(HorillaFormView):
+ """
+ Create Asset Allocation
+ """
+
+ model = AssetAssignment
+ form_class = AssetAllocationForm
+ template_name = "cbv/request_and_allocation/forms/asset_approve_form.html"
+ new_display_title = _("Asset Allocation")
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ req_id = self.kwargs.get('req_id')
+ asset_request = AssetRequest.objects.filter(id=req_id).first()
+ asset_category = asset_request.asset_category_id
+ assets = asset_category.asset_set.filter(asset_status="Available")
+ self.form.fields["asset_id"].queryset = assets
+ self.form.fields["assigned_to_employee_id"].initial = asset_request.requested_employee_id
+ self.form.fields["assigned_by_employee_id"].initial = self.request.user.employee_get
+ return context
+
+ def form_invalid(self, form: Any) -> HttpResponse:
+ if not form.is_valid():
+ errors = form.errors.as_data()
+ return render(
+ self.request, self.template_name, {"form": form, "errors": errors}
+ )
+ return super().form_invalid(form)
+
+
+ def form_valid(self, form: AssetAllocationForm) -> HttpResponse:
+ """
+ form valid function
+ """
+ req_id = self.kwargs.get('req_id')
+ asset_request = AssetRequest.objects.filter(id=req_id).first()
+ if form.is_valid():
+ asset = form.instance.asset_id.id
+ asset = Asset.objects.filter(id=asset).first()
+ asset.asset_status = "In use"
+ asset.save()
+ # form = form.save(commit=False)
+ # form.assigned_by_employee_id = self.request.user.employee_get
+ form.save()
+ asset_request.asset_request_status = "Approved"
+ asset_request.save()
+ request = getattr(_thread_locals, "request", None)
+ files = request.FILES.getlist("asset_condition_img")
+ attachments = []
+ if request.FILES:
+ for file in files:
+ attachment = ReturnImages()
+ attachment.image = file
+ attachment.save()
+ attachments.append(attachment)
+ form.instance.assign_images.add(*attachments)
+ messages.success(self.request, _("Asset request approved successfully!."))
+ notify.send(
+ self.request.user.employee_get,
+ recipient=asset_request.requested_employee_id.employee_user_id,
+ verb="Your asset request approved!.",
+ verb_ar="تم الموافقة على طلب الأصول الخاص بك!",
+ verb_de="Ihr Antragsantrag wurde genehmigt!",
+ verb_es="¡Su solicitud de activo ha sido aprobada!",
+ verb_fr="Votre demande d'actif a été approuvée !",
+ redirect=reverse("asset-request-allocation-view")
+ + f"?asset_request_date={asset_request.asset_request_date}\
+ &asset_request_status={asset_request.asset_request_status}",
+ icon="bag-check",
+ )
+ return HttpResponse("")
+ return super().form_valid(form)
+
+
+
+
+
+
+
+
+
diff --git a/asset/filters.py b/asset/filters.py
index 1a89bcea1..d89724bb6 100644
--- a/asset/filters.py
+++ b/asset/filters.py
@@ -10,11 +10,12 @@ from django.db.models import Q
from django_filters import FilterSet
from base.methods import reload_queryset
+from horilla.filters import HorillaFilterSet
-from .models import Asset, AssetAssignment, AssetCategory, AssetRequest
+from .models import Asset, AssetAssignment, AssetCategory, AssetLot, AssetRequest
-class CustomFilterSet(FilterSet):
+class CustomFilterSet(HorillaFilterSet):
"""
Custom FilterSet class that applies specific CSS classes to filter
widgets.
@@ -373,3 +374,14 @@ class AssetHistoryReGroup:
("assigned_date", "Assigned Date"),
("return_date", "Return Date"),
]
+
+
+class AssetBatchNoFilter(FilterSet):
+
+ search = django_filters.CharFilter(field_name="lot_number", lookup_expr="icontains")
+
+ class Meta:
+ model = AssetLot
+ fields = [
+ "lot_number",
+ ]
diff --git a/asset/forms.py b/asset/forms.py
index e19e482d2..84a20b379 100644
--- a/asset/forms.py
+++ b/asset/forms.py
@@ -60,6 +60,17 @@ class AssetForm(ModelForm):
attrs={"onchange": "batchNoChange($(this))"}
),
}
+ labels = {
+ "asset_name": "Asset Name",
+ "asset_description": "Description",
+ "asset_tracking_id": "Tracking ID",
+ "asset_purchase_date": "Purchase Date",
+ "expiry_date": "Expiry Date",
+ "asset_purchase_cost": "Cost",
+ "asset_category_id": "Category",
+ "asset_status": "Status",
+ "asset_lot_number_id": "Batch Number",
+ }
def __init__(self, *args, **kwargs):
request = getattr(_thread_locals, "request", None)
@@ -165,6 +176,19 @@ class AssetReportForm(ModelForm):
- __init__: Initializes the form, disabling the 'asset_id' field.
"""
+ file = forms.FileField(
+ required=False,
+ widget=forms.TextInput(
+ attrs={
+ "name": "file",
+ "type": "File",
+ "class": "form-control",
+ "multiple": "True",
+ "accept": ".jpeg, .jpg, .png, .pdf",
+ }
+ ),
+ )
+
class Meta:
"""
Metadata options for the AssetReportForm.
@@ -176,10 +200,7 @@ class AssetReportForm(ModelForm):
"""
model = AssetReport
- fields = [
- "title",
- "asset_id",
- ]
+ fields = ["title", "asset_id", "file"]
exclude = ["is_active"]
def __init__(self, *args, **kwargs):
@@ -191,7 +212,7 @@ class AssetReportForm(ModelForm):
- **kwargs: Arbitrary keyword arguments.
"""
super().__init__(*args, **kwargs)
- self.fields["asset_id"].widget.attrs["disabled"] = "disabled"
+ # self.fields["asset_id"].widget.attrs["disabled"] = "disabled"
class AssetCategoryForm(ModelForm):
@@ -199,6 +220,12 @@ class AssetCategoryForm(ModelForm):
A form for creating and updating AssetCategory instances.
"""
+ cols = {
+ "asset_category_name": 12,
+ "asset_category_description": 12,
+ "company_id": 12,
+ }
+
class Meta:
"""
Specifies the model and fields to be used for the AssetForm.
@@ -218,6 +245,8 @@ class AssetRequestForm(ModelForm):
A Django ModelForm for creating and updating AssetRequest instances.
"""
+ cols = {"requested_employee_id": 12, "asset_category_id": 12, "description": 12}
+
class Meta:
"""
Specifies the model and fields to be used for the AssetRequestForm.
@@ -258,10 +287,16 @@ class AssetRequestForm(ModelForm):
}
def __init__(self, *args, **kwargs):
- user = kwargs.pop("user", None)
- super().__init__(*args, **kwargs)
+ # user = kwargs.pop("user", None)
+ request = getattr(_thread_locals, "request", None)
+ user = request.user
+ super(AssetRequestForm, self).__init__(
+ *args,
+ **kwargs,
+ )
reload_queryset(self.fields)
if user is not None and user.has_perm("asset.add_assetrequest"):
+
self.fields["requested_employee_id"].queryset = Employee.objects.all()
self.fields["requested_employee_id"].initial = Employee.objects.filter(
id=user.employee_get.id
@@ -280,8 +315,16 @@ class AssetAllocationForm(ModelForm):
A Django ModelForm for creating and updating AssetAssignment instances.
"""
+ cols = {
+ "assigned_to_employee_id": 12,
+ "asset_id": 12,
+ "assigned_by_employee_id": 12,
+ }
+
def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
+ request = getattr(_thread_locals, "request", None)
+ user = request.user
+ super(AssetAllocationForm, self).__init__(*args, **kwargs)
reload_queryset(self.fields)
self.fields["asset_id"].queryset = Asset.objects.filter(
asset_status="Available"
@@ -310,6 +353,7 @@ class AssetAllocationForm(ModelForm):
"return_condition",
"assigned_date",
"return_images",
+ "assign_images",
"is_active",
]
widgets = {
@@ -403,6 +447,8 @@ class AssetBatchForm(ModelForm):
A Django ModelForm for creating or updating AssetLot instances.
"""
+ cols = {"lot_description": 12, "lot_number": 12}
+
class Meta:
"""
Specifies the model and fields to be used for the AssetBatchForm.
diff --git a/asset/models.py b/asset/models.py
index 64dc056dc..2d9796f8a 100644
--- a/asset/models.py
+++ b/asset/models.py
@@ -7,12 +7,15 @@ within an Asset Management System.
from django.core.exceptions import ValidationError
from django.db import models
+from django.urls import reverse, reverse_lazy
+from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from base.horilla_company_manager import HorillaCompanyManager
from base.models import Company
from employee.models import Employee
from horilla.models import HorillaModel
+from horilla_views.cbv_methods import render_template
class AssetCategory(HorillaModel):
@@ -72,6 +75,49 @@ class AssetLot(HorillaModel):
def __str__(self):
return f"{self.lot_number}"
+ def actions(self):
+ """
+ This method for get custom column for action.
+ """
+
+ return render_template(
+ path="cbv/asset_batch_no/actions.html",
+ context={"instance": self},
+ )
+
+ def asset_batch_detail(self):
+ """
+ detail view
+ """
+
+ url = reverse("asset-batch-detail-view", kwargs={"pk": self.pk})
+
+ return url
+
+ def assets_column(self):
+ """
+ This method for get custom column for action.
+ """
+
+ return render_template(
+ path="cbv/asset_batch_no/assets_col.html",
+ context={"instance": self},
+ )
+
+ def get_update_url(self):
+ """
+ This method to get update url
+ """
+ url = reverse_lazy("asset-batch-update", kwargs={"pk": self.pk})
+ return url
+
+ def get_delete_url(self):
+ """
+ This method to get delete url
+ """
+ url = reverse_lazy("asset-batch-number-delete", kwargs={"batch_id": self.pk})
+ return url
+
class Asset(HorillaModel):
"""
@@ -131,6 +177,36 @@ class Asset(HorillaModel):
def __str__(self):
return f"{self.asset_name}-{self.asset_tracking_id}"
+ def get_status_display(self):
+ """
+ Display status
+ """
+ return dict(self.ASSET_STATUS).get(self.asset_status)
+
+ def detail_view_action(self):
+ """
+ This method for get custome coloumn .
+ """
+
+ return render_template(
+ path="cbv/asset_category/detail_view_action.html",
+ context={"instance": self},
+ )
+
+ def get_update_url(self):
+ """
+ This method to get update url
+ """
+ url = reverse_lazy("asset-update", kwargs={"pk": self.pk})
+ return url
+
+ def get_delete_url(self):
+ """
+ This method to get delete url
+ """
+ url = reverse_lazy("asset-delete", kwargs={"asset_id": self.pk})
+ return url
+
def clean(self):
existing_asset = Asset.objects.filter(
asset_tracking_id=self.asset_tracking_id
@@ -274,6 +350,162 @@ class AssetAssignment(HorillaModel):
def __str__(self):
return f"{self.assigned_to_employee_id} --- {self.asset_id} --- {self.return_status}"
+ def get_avatar(self):
+ """
+ Method will retun the api to the avatar or path to the profile image
+ """
+ url = f"https://ui-avatars.com/api/?name={self.asset_id}&background=random"
+ return url
+
+ def asset_detail_view(self):
+ """
+ for detail view of page
+ """
+ url = reverse("asset-history-detail-view", kwargs={"pk": self.pk})
+ return url
+
+ def assign_condition_img(self):
+ """
+ This method for get custome coloumn .
+ """
+
+ return render_template(
+ path="cbv/asset_history/assign_condition.html",
+ context={"instance": self},
+ )
+
+ def return_condition_img(self):
+ """
+ This method for get custome coloumn .
+ """
+
+ return render_template(
+ path="cbv/asset_history/return_condition.html",
+ context={"instance": self},
+ )
+
+ def asset_action(self):
+ """
+ This method for get custom column for asset tab action.
+ """
+
+ return render_template(
+ path="cbv/request_and_allocation/asset_actions.html",
+ context={"instance": self},
+ )
+
+ def return_status_col(self):
+ """
+ This method for get custom column for return date.
+ """
+
+ return render_template(
+ path="cbv/request_and_allocation/return_status.html",
+ context={"instance": self},
+ )
+
+ def allocation_action(self):
+ """
+ This method for get custom column for asset allocation tab actions.
+ """
+
+ return render_template(
+ path="cbv/request_and_allocation/asset_allocation_action.html",
+ context={"instance": self},
+ )
+
+ def asset_detail_action(self):
+ """
+ This method for get custom column for asset detail actions.
+ """
+
+ return render_template(
+ path="cbv/request_and_allocation/asset_detail_action.html",
+ context={"instance": self},
+ )
+
+ def asset_allocation_detail_action(self):
+ """
+ This method for get custom column for asset detail actions.
+ """
+
+ return render_template(
+ path="cbv/request_and_allocation/detail_action_asset_allocation.html",
+ context={"instance": self},
+ )
+
+ def get_avatar(self):
+ """
+ Method will retun the api to the avatar or path to the question template
+ """
+ url = f"https://ui-avatars.com/api/?name={self.asset_id.asset_name}&background=random"
+ return url
+
+ def detail_view_asset(self):
+ """
+ detail view
+ """
+
+ url = reverse("asset-detail-view", kwargs={"pk": self.pk})
+ return url
+
+ def detail_view_asset_allocation(self):
+ """
+ detail view
+ """
+
+ url = reverse("asset-allocation-detail-view", kwargs={"pk": self.pk})
+ return url
+
+ def asset_detail_status(self):
+ """
+ Asset tab detail status
+ """
+
+ return (
+ 'Requested to return '
+ if self.return_request
+ else 'In use '
+ )
+
+ def detail_status(self):
+ """
+ Asset allocation tab detail status
+ """
+ if self.return_date:
+ status = 'Returned '
+ elif self.return_request:
+ status = 'Requested to return '
+ else:
+ status = 'Allocated '
+ return status
+
+ def asset_allocation_detail_subtitle(self):
+ """
+ Return subtitle containing both department and job position information.
+ """
+ return f"{self.assigned_to_employee_id.employee_work_info.department_id} / {self.assigned_to_employee_id.employee_work_info.job_position_id}"
+
+ def status_display(self):
+ status = self.asset_id.asset_status
+ color_class = "oh-dot--warning" # Adjust based on your status
+ return format_html(
+ ' '
+ '{status} ',
+ color_class=color_class,
+ status=status,
+ )
+
+ def assigned_date_display(self):
+ date_col = self.assigned_date
+ color_class = "oh-dot--success" # Adjust based on your status
+ return format_html(
+ ' '
+ '{date_col} ',
+ color_class=color_class,
+ date_col=date_col,
+ )
+
class AssetRequest(HorillaModel):
"""
@@ -314,6 +546,49 @@ class AssetRequest(HorillaModel):
verbose_name = _("Asset Request")
verbose_name_plural = _("Asset Requests")
+ def status_col(self):
+ """
+ This method for get custom coloumn for status.
+ """
+
+ return render_template(
+ path="cbv/request_and_allocation/status.html",
+ context={"instance": self},
+ )
+
+ def action_col(self):
+ """
+ This method for get custom coloumn for action.
+ """
+
+ return render_template(
+ path="cbv/request_and_allocation/asset_request_action.html",
+ context={"instance": self},
+ )
+
+ def detail_action_col(self):
+ """
+ This method for get custom coloumn for detail action.
+ """
+
+ return render_template(
+ path="cbv/request_and_allocation/asset_request_detail_action.html",
+ context={"instance": self},
+ )
+
+ def asset_request_detail_subtitle(self):
+ """
+ Return subtitle containing both department and job position information.
+ """
+ return f"{self.requested_employee_id.employee_work_info.department_id} / {self.requested_employee_id.employee_work_info.job_position_id}"
+
+ def detail_view_asset_request(self):
+ """
+ detail view
+ """
+ url = reverse("asset-request-detail-view", kwargs={"pk": self.pk})
+ return url
+
def status_html_class(self):
COLOR_CLASS = {
"Approved": "oh-dot--success",
diff --git a/asset/templates/asset/asset_information.html b/asset/templates/asset/asset_information.html
index e45792d07..331553419 100644
--- a/asset/templates/asset/asset_information.html
+++ b/asset/templates/asset/asset_information.html
@@ -133,8 +133,8 @@
diff --git a/asset/templates/category/asset_category.html b/asset/templates/category/asset_category.html
index 63176ab4d..92acc3629 100644
--- a/asset/templates/category/asset_category.html
+++ b/asset/templates/category/asset_category.html
@@ -72,7 +72,7 @@
{% trans "Edit" %}
{% endif %}
diff --git a/asset/templates/category/asset_category_view.html b/asset/templates/category/asset_category_view.html
index ad0b08263..61190dd43 100644
--- a/asset/templates/category/asset_category_view.html
+++ b/asset/templates/category/asset_category_view.html
@@ -43,8 +43,9 @@
{% endif %}
-
-
+{% include "generic/components.html" %}
+{% comment %}
+
{% endcomment %}
@@ -101,10 +102,11 @@
-
-
+
+
+
-->
@@ -191,11 +193,13 @@
{% if perms.asset.add_assetcategory %}
{% endif %}
diff --git a/asset/templates/cbv/asset_batch_no/actions.html b/asset/templates/cbv/asset_batch_no/actions.html
new file mode 100644
index 000000000..c3f6600c1
--- /dev/null
+++ b/asset/templates/cbv/asset_batch_no/actions.html
@@ -0,0 +1,45 @@
+{% load i18n %}
+{% if perms.asset.change_assetlot or perms.asset.delete_assetlot %}
+
+
+ {% if perms.asset.change_assetlot %}
+
+
+
+ {% endif %}
+ {% if perms.asset.delete_assetlot %}
+
+ {% endif %}
+
+
+ {% endif%}
\ No newline at end of file
diff --git a/asset/templates/cbv/asset_batch_no/asset_batch_no.html b/asset/templates/cbv/asset_batch_no/asset_batch_no.html
new file mode 100644
index 000000000..e9211df61
--- /dev/null
+++ b/asset/templates/cbv/asset_batch_no/asset_batch_no.html
@@ -0,0 +1,43 @@
+{% extends "index.html" %}
+{% load i18n %}{% load static %}
+
+
+{% block content %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% include "generic/components.html" %}
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/asset/templates/cbv/asset_batch_no/assets_col.html b/asset/templates/cbv/asset_batch_no/assets_col.html
new file mode 100644
index 000000000..4a41b29f5
--- /dev/null
+++ b/asset/templates/cbv/asset_batch_no/assets_col.html
@@ -0,0 +1,5 @@
+{% load i18n %}
+
+ {{instance.asset_set.count}}
+ {% trans "Assets" %}
+
\ No newline at end of file
diff --git a/asset/templates/cbv/asset_category/detail_view_action.html b/asset/templates/cbv/asset_category/detail_view_action.html
new file mode 100644
index 000000000..658811c57
--- /dev/null
+++ b/asset/templates/cbv/asset_category/detail_view_action.html
@@ -0,0 +1,19 @@
+{% load i18n %}
+{% include 'generic/horilla_detailed_view.html'%}
+
+{% if messages %}
+
+ {% for message in messages %}
+
+
+{%if message.tags == "oh-alert--success" %}
+
+ {% endif %}
+ {% endfor %}
+
+{% endif %}
\ No newline at end of file
diff --git a/asset/templates/cbv/asset_category/filter.html b/asset/templates/cbv/asset_category/filter.html
new file mode 100644
index 000000000..fd2ac9588
--- /dev/null
+++ b/asset/templates/cbv/asset_category/filter.html
@@ -0,0 +1,53 @@
+{% load i18n %}
+{% load assets_custom_filter %}
+{% load widget_tweaks %}
+
+
+ {% comment %} {% endcomment %}
+
\ No newline at end of file
diff --git a/asset/templates/cbv/asset_history/asset_history_filter.html b/asset/templates/cbv/asset_history/asset_history_filter.html
new file mode 100644
index 000000000..daeb6849e
--- /dev/null
+++ b/asset/templates/cbv/asset_history/asset_history_filter.html
@@ -0,0 +1,112 @@
+{% load static %}
+{% load i18n %}
+{% load mathfilters %}
+{% load widget_tweaks %}
+
+
+
+
+
+
+
+ {% trans "Allocated User" %}
+ {{form.assigned_to_employee_id}}
+
+
+ {% trans "Asset" %}
+ {{form.asset_id}}
+
+
+
+
+ {% trans "Asset Allocated Date" %}
+ {{ form.assigned_date |attr:"type:date" }}
+
+
+ {% trans "Status" %}
+ {{form.return_status}}
+
+
+
+
+ {% trans "Return Date" %}
+ {{ form.return_date|attr:"type:date" }}
+
+
+
+
+ {% trans "Allocated By" %}
+ {{form.assigned_by_employee_id}}
+
+
+
+
+
+
+
+
+
+
+ {% trans "Return Date Greater Or Equal" %}
+ {{form.return_date_gte}}
+
+
+ {% trans "Assign Date Greater Or Equal" %}
+ {{form.assigned_date_gte}}
+
+
+
+
+
+ {% trans "Return Date lesser Or Equal" %}
+ {{form.return_date_lte}}
+
+
+ {% trans "Assign Date Lesser Or Equal" %}
+ {{form.assigned_date_lte}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/asset/templates/cbv/asset_history/asset_history_home.html b/asset/templates/cbv/asset_history/asset_history_home.html
new file mode 100644
index 000000000..c38d5e27c
--- /dev/null
+++ b/asset/templates/cbv/asset_history/asset_history_home.html
@@ -0,0 +1,118 @@
+{% extends "index.html" %}
+{% load i18n %}
+{% load static %}
+{% block content %}
+
+
+
+
+
+
+{% include "generic/components.html" %}
+
+
+
+
+
+
+
+{% endblock content %}
\ No newline at end of file
diff --git a/asset/templates/cbv/asset_history/assign_condition.html b/asset/templates/cbv/asset_history/assign_condition.html
new file mode 100644
index 000000000..17606877d
--- /dev/null
+++ b/asset/templates/cbv/asset_history/assign_condition.html
@@ -0,0 +1,16 @@
+{% load i18n %}
+{% for doc in instance.assign_images.all %}
+
{% trans "Assign Condition Images" %}
+
diff --git a/asset/templates/cbv/asset_history/return_condition.html b/asset/templates/cbv/asset_history/return_condition.html
new file mode 100644
index 000000000..8586a37e3
--- /dev/null
+++ b/asset/templates/cbv/asset_history/return_condition.html
@@ -0,0 +1,16 @@
+{% load i18n %}
+{% for doc in instance.return_images.all %}
+
{% trans "Return Condition Images" %}
+
diff --git a/asset/templates/cbv/request_and_allocation/asset_actions.html b/asset/templates/cbv/request_and_allocation/asset_actions.html
new file mode 100644
index 000000000..a13649797
--- /dev/null
+++ b/asset/templates/cbv/request_and_allocation/asset_actions.html
@@ -0,0 +1,32 @@
+{% load i18n %}
+{% if perms.asset.change_assetassignment %}
+
+
+ {% trans "Return" %}
+
+{% else %}
+ {% if instance.return_request %}
+
+
+ {% trans "Requested to return" %}
+
+ {% else %}
+
+ {% endif %}
+{% endif %}
+
diff --git a/asset/templates/cbv/request_and_allocation/asset_allocation_action.html b/asset/templates/cbv/request_and_allocation/asset_allocation_action.html
new file mode 100644
index 000000000..7cac37ea8
--- /dev/null
+++ b/asset/templates/cbv/request_and_allocation/asset_allocation_action.html
@@ -0,0 +1,25 @@
+{% load i18n %}
+{% if not instance.return_status %}
+
+
+ {% trans "Return" %}
+
+
+{% else %}
+
+
+
+ {% trans "Returned" %}
+
+
+{% endif %}
\ No newline at end of file
diff --git a/asset/templates/cbv/request_and_allocation/asset_detail_action.html b/asset/templates/cbv/request_and_allocation/asset_detail_action.html
new file mode 100644
index 000000000..96ad86cc8
--- /dev/null
+++ b/asset/templates/cbv/request_and_allocation/asset_detail_action.html
@@ -0,0 +1,32 @@
+{% load i18n %}
+
+ {% if perms.asset.change_assetassignment or not instance.return_request %}
+
+ {% if perms.asset.change_assetassignment %}
+
+ {% trans "Return" %}
+
+ {% else %}
+ {% if not instance.return_request %}
+
+ {% endif %}
+ {% endif %}
+
+ {% endif %}
+
\ No newline at end of file
diff --git a/asset/templates/cbv/request_and_allocation/asset_request_action.html b/asset/templates/cbv/request_and_allocation/asset_request_action.html
new file mode 100644
index 000000000..0381a7186
--- /dev/null
+++ b/asset/templates/cbv/request_and_allocation/asset_request_action.html
@@ -0,0 +1,56 @@
+{% load i18n %}
+{% if perms.asset.add_assetassignment %}
+ {% if instance.asset_request_status == 'Requested' %}
+
+ {% else %}
+
+ {% endif %}
+{% endif %}
diff --git a/asset/templates/cbv/request_and_allocation/asset_request_detail_action.html b/asset/templates/cbv/request_and_allocation/asset_request_detail_action.html
new file mode 100644
index 000000000..f076a5a7d
--- /dev/null
+++ b/asset/templates/cbv/request_and_allocation/asset_request_detail_action.html
@@ -0,0 +1,30 @@
+{% load i18n %}
+
+ {% if perms.asset.add_assetassignment %}
+ {% if instance.asset_request_status == 'Requested' %}
+
+ {% endif %}
+ {% endif %}
+
\ No newline at end of file
diff --git a/asset/templates/cbv/request_and_allocation/detail_action_asset_allocation.html b/asset/templates/cbv/request_and_allocation/detail_action_asset_allocation.html
new file mode 100644
index 000000000..d22d59942
--- /dev/null
+++ b/asset/templates/cbv/request_and_allocation/detail_action_asset_allocation.html
@@ -0,0 +1,15 @@
+{% load i18n %}
+
+ {% if not instance.return_status %}
+
+ {% trans "Return" %}
+
+ {% endif %}
+
\ No newline at end of file
diff --git a/asset/templates/cbv/request_and_allocation/filter.html b/asset/templates/cbv/request_and_allocation/filter.html
new file mode 100644
index 000000000..8e10f429c
--- /dev/null
+++ b/asset/templates/cbv/request_and_allocation/filter.html
@@ -0,0 +1,144 @@
+{% load i18n %}
+
+{% load widget_tweaks %}
+
+
+
+
+
+
+
+ {% trans "Asset Name" %}
+ {{assets_filter_form.asset_id__asset_name}}
+
+
+
+
+ {% trans "Status" %}
+ {{assets_filter_form.asset_id__asset_status}}
+
+
+
+
+
+
+
+
+
+
+
+ {% trans "Requested Employee" %}
+ {{asset_request_filter_form.requested_employee_id}}
+
+
+ {% trans "Asset Category" %}
+ {{asset_request_filter_form.asset_category_id}}
+
+
+
+
+ {% trans "Asset Request Date" %}
+ {{asset_request_filter_form.asset_request_date|attr:"type:date"}}
+
+
+ {% trans "Status" %}
+ {{asset_request_filter_form.asset_request_status}}
+
+
+
+
+
+ {% if perms.asset.view_assetassignment %}
+
+
+
+
+
+
+ {% trans "Allocated User" %}
+ {{asset_allocation_filter_form.assigned_to_employee_id}}
+
+
+ {% trans "Asset" %}
+ {{asset_allocation_filter_form.asset_id}}
+
+
+
+
+ {% trans "Asset Allocated Date" %}
+ {{ asset_allocation_filter_form.assigned_date |attr:"type:date" }}
+
+
+ {% trans "Status" %}
+ {{asset_allocation_filter_form.return_status}}
+
+
+
+
+ {% trans "Return Date" %}
+ {{ asset_allocation_filter_form.return_date|attr:"type:date" }}
+
+
+
+
+ {% trans "Allocated By" %}
+ {{asset_allocation_filter_form.assigned_by_employee_id}}
+
+
+
+
+
+ {% endif %}
+
\ No newline at end of file
diff --git a/asset/templates/cbv/request_and_allocation/forms/allo_form.html b/asset/templates/cbv/request_and_allocation/forms/allo_form.html
new file mode 100644
index 000000000..f7a09219a
--- /dev/null
+++ b/asset/templates/cbv/request_and_allocation/forms/allo_form.html
@@ -0,0 +1,11 @@
+
+ {% include "generic/horilla_form.html" %}
+
+
\ No newline at end of file
diff --git a/asset/templates/cbv/request_and_allocation/forms/asset_approve_form.html b/asset/templates/cbv/request_and_allocation/forms/asset_approve_form.html
new file mode 100644
index 000000000..a0feb33a6
--- /dev/null
+++ b/asset/templates/cbv/request_and_allocation/forms/asset_approve_form.html
@@ -0,0 +1,11 @@
+
+ {% include "generic/horilla_form.html" %}
+
+
\ No newline at end of file
diff --git a/asset/templates/cbv/request_and_allocation/forms/req_form.html b/asset/templates/cbv/request_and_allocation/forms/req_form.html
new file mode 100644
index 000000000..7828e3df3
--- /dev/null
+++ b/asset/templates/cbv/request_and_allocation/forms/req_form.html
@@ -0,0 +1,8 @@
+
+ {% include "generic/horilla_form.html" %}
+
+
\ No newline at end of file
diff --git a/asset/templates/cbv/request_and_allocation/request_and_allocation.html b/asset/templates/cbv/request_and_allocation/request_and_allocation.html
new file mode 100644
index 000000000..5c264191d
--- /dev/null
+++ b/asset/templates/cbv/request_and_allocation/request_and_allocation.html
@@ -0,0 +1,132 @@
+{% extends "index.html" %}
+{% load i18n %}{% load static %}
+
+
+{% block content %}
+
+
+
+
+
+
+
+
+
+
+
+{% comment %} my_app/templates/my_app/generic/index.html {% endcomment %}
+
+
+
+{% include "generic/components.html" %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/asset/templates/cbv/request_and_allocation/return_status.html b/asset/templates/cbv/request_and_allocation/return_status.html
new file mode 100644
index 000000000..c43863c5d
--- /dev/null
+++ b/asset/templates/cbv/request_and_allocation/return_status.html
@@ -0,0 +1,20 @@
+{% load i18n %}
+{% if instance.return_date %}
+
+ {{instance.return_date}}
+
+{% else %}
+{% if instance.return_request %}
+
+
+
+ {% trans "Requested to return" %}
+
+
+{% else %}
+
+
+ {% trans "Allocated" %}
+
+{% endif %}
+{% endif %}
diff --git a/asset/templates/cbv/request_and_allocation/status.html b/asset/templates/cbv/request_and_allocation/status.html
new file mode 100644
index 000000000..7266b3883
--- /dev/null
+++ b/asset/templates/cbv/request_and_allocation/status.html
@@ -0,0 +1,10 @@
+{% load i18n %}
+
+
+
+ {% trans instance.asset_request_status %}
+
+
\ No newline at end of file
diff --git a/asset/urls.py b/asset/urls.py
index 310a24bbc..532f9543b 100644
--- a/asset/urls.py
+++ b/asset/urls.py
@@ -5,6 +5,14 @@ URL configuration for asset-related views.
from django import views
from django.urls import path
+from asset.cbv import (
+ asset_batch_no,
+ asset_category,
+ asset_history,
+ asset_tab,
+ dashboard,
+ request_and_allocation,
+)
from asset.forms import AssetCategoryForm, AssetForm
from asset.models import Asset, AssetCategory
from base.views import object_duplicate
@@ -12,28 +20,66 @@ from base.views import object_duplicate
from . import views
urlpatterns = [
+ path(
+ "asset-history/", asset_history.AssetHistoryView.as_view(), name="asset-history"
+ ),
+ path(
+ "asset-history-list/",
+ asset_history.AssetHistorylistView.as_view(),
+ name="asset-history-list",
+ ),
+ path(
+ "asset-history-nav/",
+ asset_history.AssetHistoryNavView.as_view(),
+ name="asset-history-nav",
+ ),
+ path(
+ "asset-history-detail-view/
/",
+ asset_history.AssetHistoryDetailView.as_view(),
+ name="asset-history-detail-view",
+ ),
+ # path(
+ # "asset-creation//",
+ # views.asset_creation,
+ # name="asset-creation",
+ # ),
path(
"asset-creation//",
- views.asset_creation,
+ asset_category.AssetFormView.as_view(),
name="asset-creation",
),
path("asset-list/", views.asset_list, name="asset-list"),
- path("asset-update//", views.asset_update, name="asset-update"),
+ # path("asset-update//", views.asset_update, name="asset-update"),
+ path(
+ "asset-update//",
+ asset_category.AssetFormView.as_view(),
+ name="asset-update",
+ ),
+ # path(
+ # "duplicate-asset//",
+ # object_duplicate,
+ # name="duplicate-asset",
+ # kwargs={
+ # "model": Asset,
+ # "form": AssetForm,
+ # "form_name": "asset_creation_form",
+ # "template": "asset/asset_creation.html",
+ # },
+ # ),
path(
"duplicate-asset//",
- object_duplicate,
+ asset_category.AssetDuplicateFormView.as_view(),
name="duplicate-asset",
- kwargs={
- "model": Asset,
- "form": AssetForm,
- "form_name": "asset_creation_form",
- "template": "asset/asset_creation.html",
- },
),
path("asset-delete//", views.asset_delete, name="asset-delete"),
+ # path(
+ # "asset-information//",
+ # views.asset_information,
+ # name="asset-information",
+ # ),
path(
- "asset-information//",
- views.asset_information,
+ "asset-information//",
+ asset_category.AssetCategoryDetailView.as_view(),
name="asset-information",
),
path("asset-category-view/", views.asset_category_view, name="asset-category-view"),
@@ -42,9 +88,20 @@ urlpatterns = [
views.asset_category_view_search_filter,
name="asset-category-view-search-filter",
),
+ # path(
+ # "asset-category-duplicate//",
+ # object_duplicate,
+ # name="asset-category-duplicate",
+ # kwargs={
+ # "model": AssetCategory,
+ # "form": AssetCategoryForm,
+ # "form_name": "asset_category_form",
+ # "template": "category/asset_category_creation.html",
+ # },
+ # ),
path(
"asset-category-duplicate//",
- object_duplicate,
+ asset_category.AssetCategoryDuplicateFormView.as_view(),
name="asset-category-duplicate",
kwargs={
"model": AssetCategory,
@@ -53,14 +110,29 @@ urlpatterns = [
"template": "category/asset_category_form.html",
},
),
+ path(
+ "asset-category-nav/",
+ asset_category.AssetCategoryNav.as_view(),
+ name="asset-category-nav",
+ ),
+ # path(
+ # "asset-category-creation",
+ # views.asset_category_creation,
+ # name="asset-category-creation",
+ # ),
path(
"asset-category-creation",
- views.asset_category_creation,
+ asset_category.AssetCategoryFormView.as_view(),
name="asset-category-creation",
),
+ # path(
+ # "asset-category-update/",
+ # views.asset_category_update,
+ # name="asset-category-update",
+ # ),
path(
- "asset-category-update/",
- views.asset_category_update,
+ "asset-category-update/",
+ asset_category.AssetCategoryFormView.as_view(),
name="asset-category-update",
),
path(
@@ -68,16 +140,21 @@ urlpatterns = [
views.delete_asset_category,
name="asset-category-delete",
),
+ # path(
+ # "asset-request-creation",
+ # views.asset_request_creation,
+ # name="asset-request-creation",
+ # ),
path(
"asset-request-creation",
- views.asset_request_creation,
+ request_and_allocation.AssetRequestCreateForm.as_view(),
name="asset-request-creation",
),
- path(
- "asset-request-allocation-view/",
- views.asset_request_allocation_view,
- name="asset-request-allocation-view",
- ),
+ # path(
+ # "asset-request-allocation-view/",
+ # views.asset_request_allocation_view,
+ # name="asset-request-allocation-view",
+ # ),
path(
"asset-request-individual-view/",
views.asset_request_individual_view,
@@ -108,9 +185,14 @@ urlpatterns = [
views.asset_request_reject,
name="asset-request-reject",
),
+ # path(
+ # "asset-allocate-creation",
+ # views.asset_allocate_creation,
+ # name="asset-allocate-creation",
+ # ),
path(
"asset-allocate-creation",
- views.asset_allocate_creation,
+ request_and_allocation.AssetAllocationFormView.as_view(),
name="asset-allocate-creation",
),
path(
@@ -126,20 +208,50 @@ urlpatterns = [
path("asset-excel", views.asset_excel, name="asset-excel"),
path("asset-import", views.asset_import, name="asset-import"),
path("asset-export-excel", views.asset_export_excel, name="asset-export-excel"),
+ # path(
+ # "asset-batch-number-creation",
+ # views.asset_batch_number_creation,
+ # name="asset-batch-number-creation",
+ # ),
+ # path("asset-batch-view", views.asset_batch_view, name="asset-batch-view"),
path(
"asset-batch-number-creation",
- views.asset_batch_number_creation,
+ asset_batch_no.AssetBatchCreateFormView.as_view(),
name="asset-batch-number-creation",
),
- path("asset-batch-view", views.asset_batch_view, name="asset-batch-view"),
+ path(
+ "asset-batch-view",
+ asset_batch_no.AssetBatchNoView.as_view(),
+ name="asset-batch-view",
+ ),
+ path(
+ "asset-batch-list",
+ asset_batch_no.AssetBatchNoListView.as_view(),
+ name="asset-batch-list",
+ ),
+ path(
+ "asset-batch-nav",
+ asset_batch_no.AssetBatchNoNav.as_view(),
+ name="asset-batch-nav",
+ ),
+ path(
+ "asset-batch-detail-view//",
+ asset_batch_no.AssetBatchDetailView.as_view(),
+ name="asset-batch-detail-view",
+ ),
path(
"asset-batch-number-search",
views.asset_batch_number_search,
name="asset-batch-number-search",
),
+ # path(
+ # "asset-batch-update/",
+ # views.asset_batch_update,
+ # name="asset-batch-update",
+ # ),
path(
- "asset-batch-update/",
- views.asset_batch_update,
+ "asset-batch-update/",
+ asset_batch_no.AssetBatchCreateFormView.as_view(),
name="asset-batch-update",
),
path(
@@ -149,9 +261,14 @@ urlpatterns = [
),
path("asset-count-update", views.asset_count_update, name="asset-count-update"),
path("add-asset-report/", views.add_asset_report, name="add-asset-report"),
+ # path(
+ # "add-asset-report/",
+ # views.add_asset_report,
+ # name="add-asset-report",
+ # ),
path(
- "add-asset-report/",
- views.add_asset_report,
+ "add-asset-report//",
+ asset_category.AssetReportFormView.as_view(),
name="add-asset-report",
),
path("dashboard/", views.asset_dashboard, name="asset-dashboard"),
@@ -173,11 +290,11 @@ urlpatterns = [
path(
"asset-category-chart/", views.asset_category_chart, name="asset-category-chart"
),
- path(
- "asset-history",
- views.asset_history,
- name="asset-history",
- ),
+ # path(
+ # "asset-history",
+ # views.asset_history,
+ # name="asset-history",
+ # ),
path(
"asset-history-single-view/",
views.asset_history_single_view,
@@ -188,7 +305,77 @@ urlpatterns = [
views.asset_history_search,
name="asset-history-search",
),
- path("asset-tab/", views.asset_tab, name="asset-tab"),
+ path(
+ "asset-request-allocation-view/",
+ request_and_allocation.RequestAndAllocationView.as_view(),
+ name="asset-request-allocation-view",
+ ),
+ path(
+ "list-asset-request-allocation",
+ request_and_allocation.AllocationList.as_view(),
+ name="list-asset-request-allocation",
+ ),
+ path(
+ "list-asset",
+ request_and_allocation.AssetList.as_view(),
+ name="list-asset",
+ ),
+ path(
+ "tab-asset-request-allocation",
+ request_and_allocation.RequestAndAllocationTab.as_view(),
+ name="tab-asset-request-allocation",
+ ),
+ path(
+ "list-asset-request",
+ request_and_allocation.AssetRequestList.as_view(),
+ name="list-asset-request",
+ ),
+ path(
+ "list-asset-allocation",
+ request_and_allocation.AssetAllocationList.as_view(),
+ name="list-asset-allocation",
+ ),
+ path(
+ "nav-asset-request-allocation",
+ request_and_allocation.RequestAndAllocationNav.as_view(),
+ name="nav-asset-request-allocation",
+ ),
+ path(
+ "asset-detail-view//",
+ request_and_allocation.AssetDetailView.as_view(),
+ name="asset-detail-view",
+ ),
+ path(
+ "asset-request-detail-view//",
+ request_and_allocation.AssetRequestDetailView.as_view(),
+ name="asset-request-detail-view",
+ ),
+ path(
+ "asset-request-tab-list-view//",
+ asset_tab.AssetRequestTab.as_view(),
+ name="asset-request-tab-list-view",
+ ),
+ path(
+ "assets-tab-list-view//",
+ asset_tab.AssetTabListView.as_view(),
+ name="assets-tab-list-view",
+ ),
+ path(
+ "assets-tab-list-view//",
+ asset_tab.AssetTabListView.as_view(),
+ name="assets-tab-list-view",
+ ),
+ path(
+ "asset-allocation-detail-view//",
+ request_and_allocation.AssetAllocationDetailView.as_view(),
+ name="asset-allocation-detail-view",
+ ),
+ path(
+ "asset-request-approve-form//",
+ request_and_allocation.AssetApproveFormView.as_view(),
+ name="asset-request-approve-form",
+ ),
+ path("asset-tab/", views.asset_tab, name="asset-tab"),
path(
"profile-asset-tab/",
views.profile_asset_tab,
@@ -199,9 +386,19 @@ urlpatterns = [
views.asset_request_tab,
name="asset-request-tab",
),
+ # path(
+ # "dashboard-asset-request-approve",
+ # views.dashboard_asset_request_approve,
+ # name="dashboard-asset-request-approve",
+ # ),
path(
- "main-dashboard-asset-requests",
- views.asset_dashboard_requests,
- name="main-dashboard-asset-requests",
+ "dashboard-asset-request-approve",
+ dashboard.AssetRequestToApprove.as_view(),
+ name="dashboard-asset-request-approve",
+ ),
+ path(
+ "dashboard-allocated-asset",
+ dashboard.AllocatedAssetsList.as_view(),
+ name="dashboard-allocated-asset",
),
]
diff --git a/asset/views.py b/asset/views.py
index 20d300673..d8e24d278 100644
--- a/asset/views.py
+++ b/asset/views.py
@@ -315,6 +315,19 @@ def asset_delete(request, asset_id):
)
else:
asset_del(request, asset)
+
+ if request.GET.get("instance_ids"):
+ instances_ids = request.GET.get("instance_ids")
+ instances_list = json.loads(instances_ids)
+ if asset_id in instances_list:
+ instances_list.remove(asset_id)
+ previous_instance, next_instance = closest_numbers(
+ json.loads(instances_ids), asset_id
+ )
+ return redirect(
+ f"/asset/asset-information/{next_instance}/?{previous_data}&instance_ids={instances_list}&asset_info=true"
+ )
+
if len(eval_validate(instances_ids)) <= 1:
return HttpResponse("")
@@ -741,8 +754,7 @@ def asset_allocate_creation(request):
if request.method == "POST":
form = AssetAllocationForm(request.POST)
if form.is_valid():
- asset = form.instance.asset_id.id
- asset = Asset.objects.filter(id=asset).first()
+ asset = form.instance.asset_id
asset.asset_status = "In use"
asset.save()
instance = form.save()
@@ -1496,6 +1508,9 @@ def asset_batch_number_delete(request, batch_id):
Returns:
- message of the return
"""
+ request_copy = request.GET.copy()
+ request_copy.pop("requests_ids", None)
+ previous_data = request_copy.urlencode()
previous_data = request.GET.urlencode()
try:
asset_batch_number = AssetLot.objects.get(id=batch_id)
@@ -1504,7 +1519,7 @@ def asset_batch_number_delete(request, batch_id):
)
if assigned_batch_number:
messages.error(request, _("Batch number in-use"))
- return redirect(f"/asset/asset-batch-number-search?{previous_data}")
+ return redirect(f"/asset/asset-batch-list?{previous_data}")
asset_batch_number.delete()
messages.success(request, _("Batch number deleted"))
except AssetLot.DoesNotExist:
@@ -1791,7 +1806,7 @@ def asset_history_search(request):
@login_required
@owner_can_enter("asset.view_asset", Employee)
-def asset_tab(request, emp_id):
+def asset_tab(request, pk):
"""
This function is used to view asset tab of an employee in employee individual view.
@@ -1802,7 +1817,7 @@ def asset_tab(request, emp_id):
Returns: return asset-tab template
"""
- employee = Employee.objects.get(id=emp_id)
+ employee = Employee.objects.get(id=pk)
assets_requests = employee.requested_employee.all()
assets = employee.allocated_employee.all()
assets_ids = (
@@ -1812,9 +1827,9 @@ def asset_tab(request, emp_id):
"assets": assets,
"requests": assets_requests,
"assets_ids": assets_ids,
- "employee": emp_id,
+ "employee": pk,
}
- return render(request, "tabs/asset-tab.html", context=context)
+ return render(request, "tabs/main_asset_tab.html", context=context)
@login_required
diff --git a/attendance/cbv/__init__.py b/attendance/cbv/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/attendance/cbv/accessibility.py b/attendance/cbv/accessibility.py
new file mode 100644
index 000000000..1db5a8029
--- /dev/null
+++ b/attendance/cbv/accessibility.py
@@ -0,0 +1,40 @@
+"""
+Accessiblility
+"""
+
+from django.contrib.auth.context_processors import PermWrapper
+from base.methods import check_manager
+from employee.models import Employee
+
+
+
+def attendance_accessibility(request, instance: object = None, user_perms: PermWrapper = [], *args, **kwargs) -> bool:
+ """
+ permission for attendance tab
+ """
+
+ check_manages = check_manager(request.user.employee_get, instance)
+ employee = Employee.objects.get(id=instance.pk)
+ if check_manages or request.user.has_perm("attendance.view_attendance") or request.user == employee.employee_user_id:
+ return True
+ return False
+
+def penalty_accessibility(request, instance: object = None, user_perms: PermWrapper = [], *args, **kwargs) -> bool:
+ """
+ permission for penalty tab
+ """
+
+ employee = Employee.objects.get(id=instance.pk)
+ check_manages = check_manager(request.user.employee_get,instance)
+ if request.user.has_perm("attendance.view_penaltyaccount") or request.user == employee.employee_user_id or check_manages:
+ return True
+ return False
+
+def create_attendance_request_accessibility(
+ request, instance: object = None, user_perms: PermWrapper = [], *args, **kwargs
+) -> bool:
+ employee = Employee.objects.get(id=instance.pk)
+ if ( request.user == employee.employee_user_id):
+ return True
+ return False
+
diff --git a/attendance/cbv/attendance_activity.py b/attendance/cbv/attendance_activity.py
new file mode 100644
index 000000000..e8edd6170
--- /dev/null
+++ b/attendance/cbv/attendance_activity.py
@@ -0,0 +1,220 @@
+"""
+this page is handling the cbv methods of attendance activity page
+"""
+
+from typing import Any
+from django.urls import reverse, reverse_lazy
+from django.utils.translation import gettext_lazy as _
+from django.utils.decorators import method_decorator
+from attendance.filters import AttendanceActivityFilter
+from attendance.forms import AttendanceActivityExportForm
+from attendance.models import (
+ AttendanceActivity,
+)
+from base.methods import filtersubordinates, is_reportingmanager
+from horilla_views.generic.cbv.views import (
+ HorillaDetailedView,
+ HorillaListView,
+ HorillaNavView,
+ TemplateView,
+)
+from horilla_views.cbv_methods import login_required
+
+
+@method_decorator(login_required, name="dispatch")
+class AttendanceActivityView(TemplateView):
+ """
+ for my attendance page view
+ """
+
+ template_name = "cbv/attendance_activity/attendance_activity_home.html"
+
+
+@method_decorator(login_required, name="dispatch")
+class AttendanceActivityListView(HorillaListView):
+ """
+ list view of the page
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("attendance-activity-search")
+ self.action_method = None
+ self.view_id = "deleteview"
+ if self.request.user.has_perm("attendance.delete_attendanceactivity"):
+ self.action_method = "get_delete_attendance"
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ self_attendance_activities = queryset.filter(
+ employee_id__employee_user_id=self.request.user
+ )
+ queryset = filtersubordinates(
+ self.request, queryset, "attendance.view_attendanceovertime"
+ )
+ return queryset | self_attendance_activities
+
+ filter_class = AttendanceActivityFilter
+ model = AttendanceActivity
+ records_per_page = 10
+ template_name = "cbv/attendance_activity/delete_inherit.html"
+
+ columns = [
+ (_("Employee"), "employee_id", "employee_id__get_avatar"),
+ (_("Attendance Date"), "attendance_date"),
+ (_("In Date"), "clock_in_date"),
+ (_("Check In"), "clock_in"),
+ (_("Check Out"), "clock_out"),
+ (_("Out Date"), "clock_out_date"),
+ (_("Duration (HH:MM:SS)"), "duration_format"),
+
+
+ ]
+
+ row_attrs = """
+ {diff_cell}
+ hx-get='{attendance_detail_view}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+ sortby_mapping = [
+ ("Employee", "employee_id__get_full_name", "employee_id__get_avatar"),
+ ("Attendance Date", "attendance_date"),
+ ("Check In", "clock_in"),
+ ("In Date", "clock_in_date"),
+ ("Check Out", "clock_out"),
+ ("Out Date", "clock_out_date"),
+ ("Duration (HH:MM:SS)", "duration_format")
+ ]
+
+
+@method_decorator(login_required, name="dispatch")
+class AttendanceActivityNavView(HorillaNavView):
+ """
+ nav bar
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("attendance-activity-search")
+
+ actions = [
+
+ {
+ "action": _("Import"),
+ "attrs": f"""
+ "id"="activityInfoImport"
+ data-toggle = "oh-modal-toggle"
+ data-target = "#objectCreateModal"
+ hx-target="#objectCreateModalTarget"
+ hx-get ="{reverse_lazy('attendance-activity-import')}"
+ style="cursor: pointer;"
+ """,
+ },
+ {
+ "action": _("Export"),
+ "attrs": f"""
+ data-toggle = "oh-modal-toggle"
+ data-target = "#genericModal"
+ hx-target="#genericModalBody"
+ hx-get ="{reverse_lazy('attendance-bulk-export')}"
+ style="cursor: pointer;"
+ """,
+ }
+
+ ]
+
+ if self.request.user.has_perm("attendance.delete_attendanceactivity"):
+ actions.append(
+ {
+ "action": _("Delete"),
+ "attrs": """
+ onclick="
+ deleteAttendanceNav();
+ "
+ data-action ="delete"
+ style="cursor: pointer; color:red !important"
+ """,
+ }
+ )
+ if not self.request.user.has_perm(
+ "attendance.delete_attendanceactivity"
+ ) and not is_reportingmanager(self.request):
+ actions = None
+ self.actions = actions
+
+ nav_title = _("Attendance Activity")
+ filter_body_template = "cbv/attendance_activity/filter.html"
+ filter_instance = AttendanceActivityFilter()
+ filter_form_context_name = "form"
+ search_swap_target = "#listContainer"
+
+ group_by_fields = [
+ ("employee_id", _("Employee")),
+ ("attendance_date", _("Attendance Date")),
+ ("clock_in_date", _("In Date")),
+ ("clock_out_date", _("Out Date")),
+ ("shift_day", _("Shift Day")),
+ ("employee_id__country", _("Country")),
+ ("employee_id__employee_work_info__reporting_manager_id", _("Reporting Manager")),
+ ("employee_id__employee_work_info__shift_id", _("Shift")),
+ ("employee_id__employee_work_info__work_type_id", _("Work Type")),
+ ("employee_id__employee_work_info__department_id", _("Department")),
+ ("employee_id__employee_work_info__job_position_id", _("Job Position")),
+ ("employee_id__employee_work_info__employee_type_id", _("Employement Type")),
+ ("employee_id__employee_work_info__company_id", _("Company")),
+ ]
+
+
+@method_decorator(login_required, name="dispatch")
+class AttendanceDetailView(HorillaDetailedView):
+ """
+ Detail view of page
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("attendance-activity-search")
+
+ model = AttendanceActivity
+
+ title = _("Details")
+ header = {
+ "title": "employee_id__get_full_name",
+ "subtitle": "attendance_detail_subtitle",
+ "avatar": "employee_id__get_avatar",
+ }
+ body = [
+ (_("Attendance Date"), "attendance_date"),
+ (_("Day"), "get_status"),
+ (_("Check In"), "clock_in"),
+ (_("Check In Date"), "clock_in_date"),
+ (_("Check Out"), "clock_out"),
+ (_("Check Out Date"), "clock_out_date"),
+ (_("Duration"), "duration_format"),
+ (_("Shift"), "employee_id__employee_work_info__shift_id"),
+ (_("Work Type"), "employee_id__employee_work_info__work_type_id"),
+ ]
+ action_method = "detail_view_delete_attendance"
+
+
+@method_decorator(login_required, name="dispatch")
+class AttendanceBulkExport(TemplateView):
+ """
+ for bulk export
+ """
+
+ template_name = "cbv/attendance_activity/attendance_export.html"
+
+ def get_context_data(self, **kwargs: Any):
+ """
+ get data for export
+ """
+ attendances = AttendanceActivity.objects.all()
+ export_form = AttendanceActivityExportForm
+ export = AttendanceActivityFilter(queryset=attendances)
+ context = super().get_context_data(**kwargs)
+ context["export_form"] = export_form
+ context["export"] = export
+ return context
diff --git a/attendance/cbv/attendance_request.py b/attendance/cbv/attendance_request.py
new file mode 100644
index 000000000..64c6a358b
--- /dev/null
+++ b/attendance/cbv/attendance_request.py
@@ -0,0 +1,502 @@
+"""
+Attendance requests
+"""
+
+import json
+from typing import Any
+from django.http import HttpResponse
+from django.urls import reverse
+from django.utils.decorators import method_decorator
+from django.utils.translation import gettext_lazy as _
+from django.contrib import messages
+from attendance.filters import AttendanceFilters
+from attendance.forms import (
+ AttendanceRequestForm,
+ BulkAttendanceRequestForm,
+ NewRequestForm,
+)
+from attendance.models import Attendance
+from attendance.methods.utils import get_employee_last_name
+from base.methods import choosesubordinates, filtersubordinates, is_reportingmanager
+from employee.models import Employee
+from notifications.signals import notify
+from horilla_views.cbv_methods import login_required
+from horilla_views.generic.cbv.views import (
+ HorillaDetailedView,
+ HorillaListView,
+ HorillaNavView,
+ HorillaTabView,
+ HorillaFormView,
+ TemplateView,
+)
+
+
+@method_decorator(login_required, name="dispatch")
+class AttendancesRequestView(TemplateView):
+ """
+ for attendance request page
+ """
+
+ template_name = "cbv/attendance_request/attendance_request.html"
+
+
+@method_decorator(login_required, name="dispatch")
+class AttendancesRequestTabView(HorillaTabView):
+ """
+ tabview of attendance request page
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.view_id = "attendance-container"
+ self.tabs = [
+ {
+ "title": _("Requested Attendances"),
+ "url": f"{reverse('attendance-request-list-tab')}",
+ },
+ {
+ "title": _("All Attendances"),
+ "url": f"{reverse('attendance-list-tab')}",
+ },
+ ]
+
+
+@method_decorator(login_required, name="dispatch")
+class AttendancesRequestListView(HorillaListView):
+ """
+ list view
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("attendance-request-tab")
+
+ filter_class = AttendanceFilters
+ model = Attendance
+ columns = [
+ (_("Employee"), "employee_id", "employee_id__get_avatar"),
+ (_("Date"), "attendance_date"),
+ (_("Day"), "attendance_day"),
+ (_("Check-In"), "attendance_clock_in"),
+ (_("In Date"), "attendance_clock_in_date"),
+ (_("Check-Out"), "attendance_clock_out"),
+ (_("Out Date"), "attendance_clock_out_date"),
+ (_("Shift"), "shift_id"),
+ (_("Work Type"), "work_type_id"),
+ (_("Min Hour"), "minimum_hour"),
+ (_("At Work"), "attendance_worked_hour"),
+ (_("Overtime"), "attendance_overtime"),
+
+ ]
+ sortby_mapping = [
+ ("Employee", "employee_id__get_full_name", "employee_id__get_avatar"),
+ ("Date", "attendance_date"),
+ ("In Date", "attendance_clock_in_date"),
+ ("Out Date", "attendance_clock_out_date"),
+ ("At Work", "attendance_worked_hour"),
+ ("Overtime", "attendance_overtime"),
+ ]
+ row_status_indications = [
+ (
+ "bulk-request--dot",
+ _("Bulk-Requests"),
+ """
+ onclick="
+ $('#applyFilter').closest('form').find('[name=is_bulk_request]').val('true');
+ $('[name=attendance_validated]').val('unknown').change();
+ $('#applyFilter').click();
+ "
+ """,
+ ),
+ (
+ "not-validated--dot",
+ _("Not Validated"),
+ """
+ onclick="
+ $('#applyFilter').closest('form').find('[name=attendance_validated]').val('false');
+ $('[name=is_bulk_request]').val('unknown').change();
+ $('#applyFilter').click();
+ "
+ """,
+ ),
+ (
+ "validated--dot",
+ _("Validated"),
+ """
+ onclick="
+ $('#applyFilter').closest('form').find('[name=attendance_validated]').val('true');
+ $('[name=is_bulk_request]').val('unknown').change();
+ $('#applyFilter').click();
+
+ "
+ """,
+ ),
+ ]
+
+ row_status_class = "validated-{attendance_validated}"
+
+
+@method_decorator(login_required, name="dispatch")
+class AttendanceRequestListTab(AttendancesRequestListView):
+ """
+ Attendance request tab
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.view_id = "attendance-requests-container"
+
+ template_name = "cbv/attendance_request/attendance_request_tab.html"
+
+ columns = [
+ col for col in AttendancesRequestListView.columns if col[1] != "status_col"
+ ]
+
+ action_method = "request_actions"
+ row_attrs = """
+ id = "requestedattendanceTr{get_instance_id}"
+ data-attendance-id="{get_instance_id}"
+ data-toggle="oh-modal-toggle"
+ data-target="#validateAttendanceRequest"
+ hx-get = "{detail_view}?instance_ids={ordered_ids}"
+ hx-trigger ="click"
+ hx-target="#validateAttendanceRequestModalBody"
+ """
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ self_data = queryset
+ queryset = queryset.filter(
+ is_validate_request=True,
+ )
+ queryset = filtersubordinates(
+ request=self.request,
+ perm="attendance.view_attendance",
+ queryset=queryset,
+ )
+ queryset = queryset | self_data.filter(
+ employee_id__employee_user_id=self.request.user,
+ is_validate_request=True,
+ )
+ return queryset
+
+
+@method_decorator(login_required, name="dispatch")
+class AttendanceListTab(AttendancesRequestListView):
+ """
+ Attendance tab
+ """
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ data = queryset
+ attendances = filtersubordinates(
+ request=self.request,
+ perm="attendance.view_attendance",
+ queryset=queryset,
+ )
+ queryset = attendances | data.filter(
+ employee_id__employee_user_id=self.request.user
+ )
+ queryset = queryset.filter(
+ employee_id__is_active=True,
+ )
+ return queryset
+
+ actions = [
+ {
+ "action": _("Edit"),
+ "icon": "create-outline",
+ "attrs": """
+ class="oh-btn oh-btn--light-bkg w-100"
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ hx-get="{change_attendance}"
+ hx-target="#genericModalBody"
+
+ """,
+ }
+ ]
+
+ row_attrs = """
+ {diff_cell}
+ id = "allattendanceTr{get_instance_id}"
+ hx-get='{attendance_detail_view}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ hx-trigger ="click"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+
+
+@method_decorator(login_required, name="dispatch")
+class AttendanceRequestNav(HorillaNavView):
+ """
+ nav bar
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("attendance-request-tab")
+ self.create_attrs = f"""
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ hx-get="{reverse('request-new-attendance')}"
+ hx-target="#genericModalBody"
+ """
+ if self.request.user.has_perm(
+ "attendance.add_attendanceovertime"
+ ) or is_reportingmanager(self.request):
+
+ self.actions = [
+ {
+ "action": _("Bulk Approve"),
+ "attrs": """
+ onclick="
+ reqAttendanceBulkApprove();
+ "
+ style="cursor: pointer;"
+ """,
+ },
+ {
+ "action": _("Bulk Reject"),
+ "attrs": """
+ onclick="reqAttendanceBulkReject();"
+ style="color:red !important"
+ """,
+ },
+ ]
+ else:
+ self.actions = None
+
+ nav_title = _("Attendances")
+ filter_body_template = "cbv/attendances/attendances_filter_page.html"
+ filter_instance = AttendanceFilters()
+ filter_form_context_name = "form"
+ search_swap_target = "#listContainer"
+
+ group_by_fields = [
+ ("employee_id", "Employee"),
+ ("attendance_date", "Attendance Date"),
+ ("attendance_clock_in_date", "In Date"),
+ ("attendance_clock_out_date", "Out Date"),
+ ("employee_id__country", "Country"),
+ ("employee_id__employee_work_info__reporting_manager_id", "Reporting Manager"),
+ ("shift_id", "Shift"),
+ ("work_type_id", "Work Type"),
+ ("minimum_hour", " Min Hour"),
+ ("employee_id__employee_work_info__department_id", "Department"),
+ ("employee_id__employee_work_info__job_position_id", "Job Position"),
+ ("employee_id__employee_work_info__employee_type_id", "Employement Type"),
+ ("employee_id__employee_work_info__company_id", "Company"),
+ ]
+
+
+@method_decorator(login_required, name="dispatch")
+class AttendanceListTabDetailView(HorillaDetailedView):
+ """
+ Detail view of page
+ """
+
+ model = Attendance
+
+ title = _("Details")
+ header = {
+ "title": "employee_id__get_full_name",
+ "subtitle": "attendances_detail_subtitle",
+ "avatar": "employee_id__get_avatar",
+ }
+ body = [
+ (_("Date"), "attendance_date"),
+ (_("Day"), "attendance_day"),
+ (_("Check-In"), "attendance_clock_in"),
+ (_("Check In Date"), "attendance_clock_in_date"),
+ (_("Check-Out"), "attendance_clock_out"),
+ (_("Check Out Date"), "attendance_clock_out_date"),
+ (_("Shift"), "shift_id"),
+ (_("Work Type"), "work_type_id"),
+ (_("Min Hour"), "minimum_hour"),
+ (_("At Work"), "attendance_worked_hour"),
+ (_("Overtime"), "attendance_overtime"),
+ (_("Activities"), "attendance_detail_activity_col", True),
+ ]
+
+ actions = [
+ {
+ "action": _("Edit"),
+ "icon": "create-outline",
+ "attrs": """
+ onclick="event.stopPropagation();"
+ class="oh-btn oh-btn--info w-100"
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModalEdit"
+ hx-get="{change_attendance}?all_attendance=true"
+ hx-target="#genericModalEditBody"
+
+ """,
+ }
+ ]
+
+
+class NewAttendanceRequestFormView(HorillaFormView):
+ """
+ form view for create attendance request
+ """
+
+ form_class = NewRequestForm
+ model = Attendance
+ new_display_title = _("New Attendance Request")
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.view_id = "attendanceRequest"
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ self.form = choosesubordinates(
+ self.request, self.form, "attendance.change_attendance"
+ )
+ self.form.fields["employee_id"].queryset = self.form.fields[
+ "employee_id"
+ ].queryset | Employee.objects.filter(employee_user_id=self.request.user)
+ self.form.fields["employee_id"].initial = self.request.user.employee_get.id
+ if self.request.GET.get("emp_id"):
+ emp_id = self.request.GET.get("emp_id")
+ self.form.fields["employee_id"].queryset = Employee.objects.filter(
+ id=emp_id
+ )
+ self.form.fields["employee_id"].initial = emp_id
+ if self.form.instance.pk:
+ self.form_class.verbose_name = _("Update Attendance Request")
+ return context
+
+ def form_valid(self, form: NewRequestForm) -> HttpResponse:
+ if form.is_valid():
+ message = _("New Attendance request created")
+ if form.new_instance is not None:
+ form.new_instance.save()
+ messages.success(self.request, message)
+ return self.HttpResponse()
+ return super().form_valid(form)
+
+
+class BulkAttendanceRequestFormView(HorillaFormView):
+ """
+ form view for create bulk attendance request
+ """
+
+ form_class = BulkAttendanceRequestForm
+ model = Attendance
+ new_display_title = _("New Attendance Request")
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ self.form = choosesubordinates(
+ self.request, self.form, "attendance.change_attendance"
+ )
+ self.form.fields["employee_id"].queryset = self.form.fields[
+ "employee_id"
+ ].queryset | Employee.objects.filter(employee_user_id=self.request.user)
+ self.form.fields["employee_id"].initial = self.request.user.employee_get.id
+ return context
+
+ def form_valid(self, form: BulkAttendanceRequestForm) -> HttpResponse:
+ form.instance.attendance_clock_in_date = self.request.POST.get("from_date")
+ form.instance.attendance_date = self.request.POST.get("from_date")
+ if form.is_valid():
+ if form.instance.pk:
+ message = _("New Attendance request updated")
+ else:
+ message = _("New Attendance request created")
+ instance = form.save(commit=False)
+ messages.success(self.request, message)
+ return self.HttpResponse()
+ return super().form_valid(form)
+
+
+class UpdateAttendanceRequestFormView(HorillaFormView):
+ """
+ form view for update attendance request
+ """
+
+ form_class = AttendanceRequestForm
+ model = Attendance
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.view_id = "attendanceRequest"
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ if self.form.instance.pk:
+ self.form_class.verbose_name = _("Update Attendance Request")
+ return context
+
+ def form_valid(self, form: AttendanceRequestForm) -> HttpResponse:
+ if form.is_valid():
+ attendance = Attendance.objects.get(id=self.form.instance.pk)
+ instance = form.save()
+ instance.employee_id = attendance.employee_id
+ instance.id = attendance.id
+ if attendance.request_type != "create_request":
+ attendance.requested_data = json.dumps(instance.serialize())
+ attendance.request_description = instance.request_description
+ # set the user level validation here
+ attendance.is_validate_request = True
+ attendance.save()
+ else:
+ instance.is_validate_request_approved = False
+ instance.is_validate_request = True
+ instance.save()
+ messages.success(self.request, _("Attendance update request created."))
+ employee = attendance.employee_id
+ if attendance.employee_id.employee_work_info.reporting_manager_id:
+ reporting_manager = (
+ attendance.employee_id.employee_work_info.reporting_manager_id.employee_user_id
+ )
+ user_last_name = get_employee_last_name(attendance)
+ notify.send(
+ self.request.user,
+ recipient=reporting_manager,
+ verb=f"{employee.employee_first_name} {user_last_name}'s\
+ attendance update request for {attendance.attendance_date} is created",
+ verb_ar=f"تم إنشاء طلب تحديث الحضور لـ {employee.employee_first_name} \
+ {user_last_name }في {attendance.attendance_date}",
+ verb_de=f"Die Anfrage zur Aktualisierung der Anwesenheit von \
+ {employee.employee_first_name} {user_last_name} \
+ für den {attendance.attendance_date} wurde erstellt",
+ verb_es=f"Se ha creado la solicitud de actualización de asistencia para {employee.employee_first_name}\
+ {user_last_name} el {attendance.attendance_date}",
+ verb_fr=f"La demande de mise à jour de présence de {employee.employee_first_name}\
+ {user_last_name} pour le {attendance.attendance_date} a été créée",
+ redirect=reverse("request-attendance-view")
+ + f"?id={attendance.id}",
+ icon="checkmark-circle-outline",
+ )
+ detail_view = self.request.GET.get("detail_view")
+ all_attendance = self.request.GET.get("all_attendance")
+ if detail_view == "true":
+ return HttpResponse(
+ f"""
+ """
+ )
+ elif all_attendance == "true":
+ return HttpResponse(
+ f"""
+ """
+ )
+
+ return self.HttpResponse()
+ return super().form_valid(form)
diff --git a/attendance/cbv/attendance_tab.py b/attendance/cbv/attendance_tab.py
new file mode 100644
index 000000000..80a213aa5
--- /dev/null
+++ b/attendance/cbv/attendance_tab.py
@@ -0,0 +1,124 @@
+"""
+This page is handling the cbv methods of work type and shift tab in employee profile page.
+"""
+
+import json
+from typing import Any
+from django.utils.translation import gettext_lazy as _
+from django.urls import reverse
+from attendance.cbv.attendance_request import AttendanceRequestListTab
+from attendance.cbv.hour_account import HourAccountList
+from attendance.cbv.my_attendances import MyAttendancesListView
+from attendance.filters import AttendanceFilters
+from attendance.models import Attendance
+from base.methods import filtersubordinates
+from base.request_and_approve import paginator_qry
+from employee.models import Employee
+from horilla_views.generic.cbv.views import HorillaListView, HorillaTabView
+
+class AttendanceTabView(HorillaTabView):
+ """
+ generic tab view for attendance
+ """
+
+ # template_name = "cbv/work_shift_tab/extended_work-shift.html"
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.view_id = "attendance-container"
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ pk = self.kwargs.get("pk")
+ context["emp_id"] = pk
+ employee = Employee.objects.get(id=pk)
+ context["instance"] = employee
+ context["tabs"] = [
+ {
+ "title": _("Requested Attendances"),
+ "url": f"{reverse('attendance-request-individual-tab',kwargs={'pk': pk})}",
+ "actions": [
+ {
+ "action": "Create Attendance Request",
+ "accessibility": "attendance.cbv.accessibility.create_attendance_request_accessibility",
+ "attrs": f"""
+ hx-get="{reverse('request-new-attendance')}?emp_id={pk}",
+ hx-target="#genericModalBody"
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ """,
+ }
+ ],
+ },
+ {
+ "title": _("Validate Attendances"),
+ "url": f"{reverse('validate-attendance-individual-tab',kwargs={'pk': pk})}",
+ },
+ {
+ "title": _("Hour Account"),
+ "url": f"{reverse('attendance-overtime-individual-tab',kwargs={'pk': pk})}",
+ },
+ {
+ "title": _("All Attendances"),
+ "url": f"{reverse('all-attendances-individual-tab',kwargs={'pk': pk})}",
+ },
+ ]
+ return context
+
+
+class RequestedAttendanceIndividualView(AttendanceRequestListTab):
+ """
+ list view for requested attendance tab view
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ pk = self.request.resolver_match.kwargs.get('pk')
+ self.search_url = reverse("attendance-request-individual-tab",kwargs= {'pk': pk} )
+ self.view_id = "attendance-requests-container"
+
+ def get_queryset(self):
+ queryset = HorillaListView.get_queryset(self)
+ pk = self.request.resolver_match.kwargs.get('pk')
+ queryset = queryset.filter(
+ employee_id__employee_user_id=pk,
+ is_validate_request=True,
+ )
+ return queryset
+
+
+class HourAccountIndividualTabView(HourAccountList):
+ """
+ list view for hour account tab
+ """
+
+ template_name = "cbv/hour_account/hour_account_main.html"
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ pk = self.request.resolver_match.kwargs.get('pk')
+ self.search_url = reverse("attendance-overtime-individual-tab",kwargs= {'pk': pk} )
+ self.view_id = "ot-table"
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ pk = self.kwargs.get("pk")
+ queryset = queryset.filter(employee_id=pk)
+ return queryset
+
+
+class AllAttendancesList(MyAttendancesListView):
+
+ def get_context_data(self, **kwargs: Any):
+ context = super().get_context_data(**kwargs)
+ pk = self.kwargs.get("pk")
+ context["search_url"] = (
+ f"{reverse('all-attendances-individual-tab',kwargs={'pk': pk})}"
+ )
+ return context
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ pk = self.kwargs.get("pk")
+ queryset = queryset.filter(employee_id=pk)
+ return queryset
diff --git a/attendance/cbv/attendances.py b/attendance/cbv/attendances.py
new file mode 100644
index 000000000..010051d11
--- /dev/null
+++ b/attendance/cbv/attendances.py
@@ -0,0 +1,731 @@
+"""
+this page is handling the cbv methods of attendances page
+"""
+
+import datetime
+from typing import Any
+from django.http import HttpResponse
+from django.shortcuts import render
+from django.urls import resolve, reverse, reverse_lazy
+from django.contrib import messages
+from django.utils.translation import gettext_lazy as _
+from django.utils.decorators import method_decorator
+import django_filters
+from attendance.cbv.attendance_activity import AttendanceActivityListView
+from attendance.filters import AttendanceFilters
+from attendance.forms import AttendanceExportForm, AttendanceForm, AttendanceUpdateForm
+from attendance.cbv.attendance_tab import AttendanceTabView
+from attendance.models import (
+ Attendance,
+ AttendanceValidationCondition,
+ strtime_seconds,
+)
+from base.decorators import manager_can_enter
+from base.filters import PenaltyFilter
+from base.methods import choosesubordinates, filtersubordinates, is_reportingmanager
+from base.models import PenaltyAccounts
+from employee.cbv.employee_profile import EmployeeProfileView
+from employee.cbv.employees import EmployeeNav, EmployeeCard, EmployeesList
+from employee.filters import EmployeeFilter
+from employee.models import Employee
+from horilla.filters import HorillaFilterSet
+from horilla_views.generic.cbv.views import (
+ HorillaDetailedView,
+ HorillaListView,
+ HorillaNavView,
+ HorillaTabView,
+ TemplateView,
+ HorillaFormView,
+)
+from horilla_views.cbv_methods import login_required, render_template
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.view_attendance"), name="dispatch")
+class AttendancesView(TemplateView):
+ """
+ for attendances page
+ """
+
+ template_name = "cbv/attendances/attendance_view_page.html"
+
+
+class AttendancesListView(HorillaListView):
+ """
+ list view
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("attendances-list-view")
+ if self.request.user.has_perm(
+ "attendance.change_attendance"
+ ) or is_reportingmanager(self.request):
+ self.option_method = "attendance_actions"
+
+ filter_class = AttendanceFilters
+ model = Attendance
+ columns = [
+ (_("Employee"), "employee_id", "employee_id__get_avatar"),
+ (_("Date"), "attendance_date"),
+ (_("Day"), "attendance_day"),
+ (_("Check-In"), "attendance_clock_in"),
+ (_("In Date"), "attendance_clock_in_date"),
+ (_("Check-Out"), "attendance_clock_out"),
+ (_("Out Date"), "attendance_clock_out_date"),
+ (_("Shift"), "shift_id"),
+ (_("Work Type"), "work_type_id"),
+ (_("Min Hour"), "minimum_hour"),
+ (_("At Work"), "attendance_worked_hour"),
+ (_("Pending Hour"), "hours_pending"),
+ (_("Overtime"), "attendance_overtime"),
+ ]
+ sortby_mapping = [
+ ("Employee", "employee_id__get_full_name", "employee_id__get_avatar"),
+ ("Date", "attendance_date"),
+ ("Day", "attendance_day__day"),
+ ("Check-In", "attendance_clock_in"),
+ ("In Date", "attendance_clock_in_date"),
+ ("Check-Out", "attendance_clock_out"),
+ ("Out Date", "attendance_clock_out_date"),
+ ("Shift", "shift_id__employee_shift"),
+ ("Work Type", "work_type_id__work_type"),
+ ("Min Hour", "minimum_hour"),
+ ("At Work", "attendance_worked_hour"),
+ ("Pending Hour", "hours_pending"),
+ ("Overtime", "attendance_overtime"),
+ ]
+ records_per_page = 10
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.view_attendance"), name="dispatch")
+class AttendancesTabView(HorillaTabView):
+ """
+ tabview of candidate page
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.view_id = "attendances-tab"
+ self.tabs = [
+ {
+ "title": _("Attendance To Validate"),
+ "url": f"{reverse('validate-attendance-tab')}",
+ "actions": [
+ {
+ "action": "Validate",
+ "attrs": """
+ onclick="
+ bulkValidateTabAttendance();
+ "
+ style="cursor: pointer;"
+ """,
+ }
+ ],
+ },
+ {
+ "title": _(" OT Attendances"),
+ "url": f"{reverse('ot-attendance-tab')}",
+ "actions": [
+ {
+ "action": "Approve OT",
+ "attrs": """
+ onclick="
+ otBulkValidateTabAttendance();
+ "
+ style="cursor: pointer;"
+ """,
+ }
+ ],
+ },
+ {
+ "title": _(" Validated Attendances"),
+ "url": f"{reverse('validated-attendance-tab')}",
+ },
+ ]
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.view_attendance"), name="dispatch")
+class AttendancesNavView(HorillaNavView):
+ """
+ nav bar
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("attendances-tab-view")
+ self.search_in = [
+ ("attendance_day", _("Day")),
+ ("shift_id", _("Shift")),
+ ("work_type_id", _("Work Type")),
+ ("employee_id__employee_work_info__department_id", _("Department")),
+ (
+ "employee_id__employee_work_info__job_position_id",
+ _("Job Position"),
+ ),
+ ("employee_id__employee_work_info__company_id", _("Company")),
+ ]
+ self.create_attrs = f"""
+ hx-get="{reverse_lazy("attendance-create")}"
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+ actions = [
+ {
+ "action": _("Import"),
+ "attrs": """
+ onclick="
+ importAttendanceNav();
+ "
+ data-toggle = "oh-modal-toggle"
+ data-target = "#attendanceImport
+ "
+ style="cursor: pointer;"
+ """,
+ },
+ {
+ "action": _("Export"),
+ "attrs": f"""
+ data-toggle = "oh-modal-toggle"
+ data-target = "#genericModal"
+ hx-target="#genericModalBody"
+ hx-get ="{reverse('attendences-navbar-export')}"
+ style="cursor: pointer;"
+ """,
+ },
+ ]
+ if self.request.user.has_perm("attendance.add_attendance"):
+ actions.append(
+ {
+ "action": _("Delete"),
+ "attrs": """
+ onclick="
+ bulkDeleteAttendanceNav();
+ "
+ data-action="delete"
+ style="cursor: pointer; color:red !important"
+ """,
+ }
+ )
+ self.actions = actions
+
+ nav_title = _("Attendances")
+ filter_body_template = "cbv/attendances/attendances_filter_page.html"
+ filter_instance = AttendanceFilters()
+ filter_form_context_name = "form"
+ search_swap_target = "#listContainer"
+
+ group_by_fields = [
+ ("employee_id", _("Employee")),
+ ("attendance_date", _("Attendance Date")),
+ ("shift_id", _("Shift")),
+ ("Work Type", _("work_type_id")),
+ ("minimum_hour", _("Min Hour")),
+ ("employee_id__country", "Country"),
+ (
+ "employee_id__employee_work_info__reporting_manager_id",
+ _("Reporting Manager"),
+ ),
+ ("employee_id__employee_work_info__department_id", _("Department")),
+ ("employee_id__employee_work_info__job_position_id", _("Job Position")),
+ (
+ "employee_id__employee_work_info__employee_type_id",
+ _("Employement Type"),
+ ),
+ ("employee_id__employee_work_info__company_id", _("Company")),
+ ]
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.view_attendance"), name="dispatch")
+class AttendancesExportNav(TemplateView):
+ """
+ for bulk export
+ """
+
+ template_name = "cbv/attendances/attendances_export_page.html"
+
+ def get_context_data(self, **kwargs: Any):
+ """
+ get data for export
+ """
+
+ attendances = Attendance.objects.all()
+ export_form = AttendanceExportForm
+ export = AttendanceFilters(queryset=attendances)
+ context = super().get_context_data(**kwargs)
+ context["export_form"] = export_form
+ context["export"] = export
+ return context
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.view_attendance"), name="dispatch")
+class ValidateAttendancesList(AttendancesListView):
+ """
+ validate tab
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("validate-attendance-tab")
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ queryset = queryset.filter(
+ attendance_validated=False, employee_id__is_active=True
+ )
+ queryset = filtersubordinates(
+ self.request, queryset, "attendance.view_attendance"
+ )
+ return queryset
+
+ selected_instances_key_id = "validateselectedInstances"
+ action_method = "validate_button"
+ row_attrs = """
+ hx-get='{validate_detail_view}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+
+ header_attrs = {
+ "action": """
+ style="width:150px !important;"
+ """
+ }
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.view_attendance"), name="dispatch")
+class OTAttendancesList(AttendancesListView):
+ """
+ OT tab
+ """
+
+ selected_instances_key_id = "overtimeselectedInstances"
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("ot-attendance-tab")
+ self.action_method = "ot_approve"
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ minot = strtime_seconds("00:30")
+ condition = (
+ AttendanceValidationCondition.objects.first()
+ ) # and condition.minimum_overtime_to_approve is not None
+ if condition is not None:
+ minot = strtime_seconds(condition.minimum_overtime_to_approve)
+ queryset = queryset.filter(
+ overtime_second__gt=0,
+ attendance_validated=True,
+ )
+ queryset = filtersubordinates(
+ self.request, queryset, "attendance.view_attendance"
+ )
+ return queryset
+
+ row_attrs = """
+ hx-get='{ot_detail_view}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+ header_attrs = {
+ "action": """
+ style="width:150px !important;"
+ """
+ }
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.view_attendance"), name="dispatch")
+class ValidatedAttendancesList(AttendancesListView):
+ """
+ validated tab
+ """
+
+ selected_instances_key_id = "validatedselectedInstances"
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("validated-attendance-tab")
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ queryset = queryset.filter(
+ attendance_validated=True, employee_id__is_active=True
+ )
+ queryset = filtersubordinates(
+ self.request, queryset, "attendance.view_attendance"
+ )
+ return queryset
+
+ row_attrs = """
+ hx-get='{validated_detail_view}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.view_attendance"), name="dispatch")
+class GenericAttendancesDetailView(HorillaDetailedView):
+ """
+ Generic Detail view of page
+ """
+
+ model = Attendance
+
+ title = _("Details")
+ header = {
+ "title": "employee_id__get_full_name",
+ "subtitle": "attendances_detail_subtitle",
+ "avatar": "employee_id__get_avatar",
+ }
+ body = [
+ (_("Date"), "attendance_date"),
+ (_("Day"), "attendance_day"),
+ (_("Check-In"), "attendance_clock_in"),
+ (_("Check In Date"), "attendance_clock_in_date"),
+ (_("Check-Out"), "attendance_clock_out"),
+ (_("Check Out Date"), "attendance_clock_out_date"),
+ (_("Shift"), "shift_id"),
+ (_("Work Type"), "work_type_id"),
+ (_("Min Hour"), "minimum_hour"),
+ (_("At Work"), "attendance_worked_hour"),
+ (_("Overtime"), "attendance_overtime"),
+ (_("Activities"), "attendance_detail_activity_col", True),
+ ]
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.view_attendance"), name="dispatch")
+class ValidateDetailView(GenericAttendancesDetailView):
+ """
+ detail view for validate tab
+ """
+
+ action_method = "validate_detail_actions"
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.view_attendance"), name="dispatch")
+class OtDetailView(GenericAttendancesDetailView):
+ """
+ detail view for OT tab
+ """
+
+ action_method = "ot_detail_actions"
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.view_attendance"), name="dispatch")
+class ValidatedDetailView(GenericAttendancesDetailView):
+ """
+ detail view for validate tab
+ """
+
+ action_method = "validated_detail_actions"
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.add_attendance"), name="dispatch")
+class AttendancesFormView(HorillaFormView):
+ """
+ form view
+ """
+
+ form_class = AttendanceForm
+ model = Attendance
+ new_display_title = _("Add Attendances")
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ self.form = choosesubordinates(
+ self.request, self.form, "attendance.add_attendance"
+ )
+
+ context["form"] = self.form
+ context["view_id"] = "attendanceCreate"
+
+ return context
+
+ def form_valid(self, form: AttendanceForm) -> HttpResponse:
+ if form.is_valid():
+ message = _("Attendance Added")
+ form.save()
+ messages.success(self.request, message)
+ return self.HttpResponse("")
+ return super().form_valid(form)
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.change__attendance"), name="dispatch")
+class AttendanceUpdateFormView(HorillaFormView):
+ """
+ form for update
+ """
+
+ model = Attendance
+ form_class = AttendanceUpdateForm
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ if self.form.instance.pk:
+ self.form_class.verbose_name = _("Edit Attendance")
+
+ context["view_id"] = "attendanceUpdate"
+
+ return context
+
+ def form_valid(self, form: AttendanceUpdateForm) -> HttpResponse:
+ if form.is_valid():
+ message = _("Attandance Updated")
+ form.save()
+ messages.success(self.request, message)
+ return HttpResponse("")
+ return super().form_valid(form)
+
+
+@method_decorator(login_required, name="dispatch")
+class AttendanceDetailActivityList(AttendanceActivityListView):
+ """
+ List view for activity col in detail view
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.action_method = None
+ resolved = resolve(self.request.path_info)
+ kwargs = {
+ "pk": resolved.kwargs.get("pk"),
+ }
+ self.search_url = reverse("get-attendance-activities", kwargs=kwargs)
+
+ bulk_select_option = None
+ row_attrs = ""
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ pk = Attendance.find(self.kwargs.get("pk"))
+ queryset = queryset.filter(
+ attendance_date=pk.attendance_date, employee_id=pk.employee_id
+ )
+
+ return queryset
+
+
+class PenaltyAccountListView(HorillaListView):
+ """
+ list view for penalty tab
+ """
+
+ filter_class = PenaltyFilter
+ model = PenaltyAccounts
+ records_per_page = 3
+ columns = [
+ (_("Leave Type"), "leave_type_id"),
+ (_("Minus Days"), "minus_leaves"),
+ (_("Deducted From CFD"), "get_deduct_from_carry_forward"),
+ (_("Penalty amount"), "penalty_amount"),
+ (_("Created Date"), "created_at"),
+ (_("Penalty Type"),"penalty_type_col")
+ ]
+
+ actions = [
+
+ {
+ "action": _("Delete"),
+ "icon": "trash-outline",
+ "attrs" : """
+ class="oh-btn oh-btn--light-bkg w-100 text-danger"
+ hx-confirm="Are you sure you want to delete this penalty?"
+ hx-post="{get_delete_url}"
+ hx-target="#penaltyTr{get_delete_instance}"
+ hx-swap="delete"
+
+ """
+ }
+
+ ]
+
+ row_attrs = """
+ id = "penaltyTr{get_delete_instance}"
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ pk = self.request.resolver_match.kwargs.get("pk")
+ self.search_url = reverse("individual-panlty-list-view", kwargs={"pk": pk})
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ pk = self.kwargs.get("pk")
+ queryset = queryset.filter(employee_id=pk)
+ return queryset
+
+
+class ValidateAttendancesIndividualTabView(AttendancesListView):
+ """
+ list view for validate attendance tab view
+ """
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ pk = self.kwargs.get("pk")
+ queryset = queryset.filter(
+ employee_id=pk,
+ attendance_validated=False,
+ employee_id__is_active=True,
+ )
+ queryset = (
+ filtersubordinates(self.request, queryset, "attendance.view_attendance")
+ | queryset
+ )
+ return queryset
+
+ selected_instances_key_id = "validateselectedInstances"
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ pk = self.request.resolver_match.kwargs.get("pk")
+ self.search_url = reverse(
+ "validate-attendance-individual-tab", kwargs={"pk": pk}
+ )
+ if self.request.user.has_perm(
+ "attendance.change_attendance"
+ ) or is_reportingmanager(self.request):
+ self.action_method = "validate_button"
+ self.view_id = "validate-container"
+
+ row_attrs = """
+ hx-get='{individual_validate_detail_view}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+
+
+class ValidateAttendancesIndividualDetailView(GenericAttendancesDetailView):
+ """
+ Validate tab detail view in single view of employee
+ """
+
+ action_method = "validate_detail_actions"
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ pk = self.kwargs.get("pk")
+ obj = queryset.get(pk=pk)
+ employee_id = obj.employee_id
+ if is_reportingmanager(self.request):
+ queryset = filtersubordinates(
+ self.request, queryset, "attendance.view_attendance"
+ ) | queryset.filter(employee_id=self.request.user.employee_get)
+ elif self.request.user.has_perm("attendance.view_attendance"):
+ queryset = queryset.filter(employee_id=employee_id)
+ else:
+ queryset = queryset.filter(employee_id=self.request.user.employee_get)
+ return queryset
+
+ @method_decorator(login_required, name="dispatch")
+ def dispatch(self, *args, **kwargs):
+ return super(GenericAttendancesDetailView, self).dispatch(*args, **kwargs)
+
+
+EmployeeProfileView.add_tab(
+ tabs=[
+ {
+ "title": "Attendance",
+ # "view": views.attendance_tab,
+ "view": AttendanceTabView.as_view(),
+ "accessibility": "attendance.cbv.accessibility.attendance_accessibility",
+ },
+ {
+ "title": "Penalty Account",
+ "view": PenaltyAccountListView.as_view(),
+ "accessibility": "attendance.cbv.accessibility.penalty_accessibility",
+ },
+ ]
+)
+
+
+def get_working_today(queryset, _name, value):
+ today = datetime.datetime.now().date()
+ yesterday = today - datetime.timedelta(days=1)
+
+ working_employees = Attendance.objects.filter(
+ attendance_date__gte=yesterday,
+ attendance_date__lte=today,
+ attendance_clock_out_date__isnull=True,
+ ).values_list("employee_id", flat=True)
+
+ if value:
+ queryset = queryset.filter(id__in=working_employees)
+ else:
+ queryset = queryset.exclude(id__in=working_employees)
+ return queryset
+
+
+og_init = EmployeeFilter.__init__
+
+
+def online_init(self, *args, **kwargs):
+ og_init(self, *args, **kwargs)
+ custom_field = django_filters.BooleanFilter(
+ label="Working", method=get_working_today
+ )
+ self.filters["working_today"] = custom_field
+ self.form.fields["working_today"] = custom_field.field
+ self.form.fields["working_today"].widget.attrs.update(
+ {
+ "class": "oh-select oh-select-2 w-100",
+ }
+ )
+
+
+status_indications = [
+ (
+ "offline--dot",
+ _("Offline"),
+ """
+ onclick="
+ $('#applyFilter').closest('form').find('[name=working_today]').val('false');
+ $('#applyFilter').click();
+ "
+ """,
+ ),
+ (
+ "online--dot",
+ _("Online"),
+ """
+ onclick="$('#applyFilter').closest('form').find('[name=working_today]').val('true');
+ $('#applyFilter').click();
+ "
+ """,
+ ),
+]
+
+
+def offline_online(self):
+ """
+ This method for get custome coloumn for rating.
+ """
+
+ return render_template(
+ path="cbv/employees_view/offline_online.html",
+ context={"instance": self},
+ )
+
+
+EmployeeFilter.__init__ = online_init
+EmployeeNav.filter_instance = EmployeeFilter()
+EmployeeCard.card_status_indications = status_indications
+EmployeesList.row_status_indications = status_indications
+Employee.offline_online = offline_online
diff --git a/attendance/cbv/break_point.py b/attendance/cbv/break_point.py
new file mode 100644
index 000000000..9213fe4b6
--- /dev/null
+++ b/attendance/cbv/break_point.py
@@ -0,0 +1,120 @@
+"""
+this page is handling the cbv methods for Break point conditions in settings
+"""
+
+from typing import Any
+from django.http import HttpResponse
+from django.shortcuts import render
+from django.urls import reverse
+from django.contrib import messages
+from django.utils.decorators import method_decorator
+from django.utils.translation import gettext_lazy as _
+from attendance.filters import AttendanceBreakpointFilter
+from attendance.forms import AttendanceValidationConditionForm
+from attendance.models import AttendanceValidationCondition
+from horilla.decorators import permission_required
+from horilla_views.cbv_methods import login_required
+from horilla_views.generic.cbv.views import (
+ HorillaFormView,
+ HorillaListView,
+ HorillaNavView,
+)
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(
+ permission_required("attendance.view_attendancevalidationcondition"),
+ name="dispatch",
+)
+class BreakPointList(HorillaListView):
+ """
+ list view of the Break point conditions in settings
+ """
+
+ model = AttendanceValidationCondition
+ filter_class = AttendanceBreakpointFilter
+
+ columns = [
+ (_("Auto Validate Till"), "validation_at_work"),
+ (_("Min Hour To Approve OT"), "minimum_overtime_to_approve"),
+ (_("OT Cut-Off/Day"), "overtime_cutoff"),
+ (_("Actions"), "break_point_actions"),
+ ]
+ header_attrs = {
+ "validation_at_work": """ style="width:200px !important" """,
+
+ }
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(
+ permission_required("attendance.view_attendancevalidationcondition"),
+ name="dispatch",
+)
+class BreakPointNavView(HorillaNavView):
+ """
+ navbar of attendance breakpoint view
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ condition = AttendanceValidationCondition.objects.first()
+ if not condition and self.request.user.has_perm("attendance.add_attendancevalidationcondition"):
+ self.create_attrs = f"""
+ onclick = "event.stopPropagation();"
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ hx-target="#genericModalBody"
+ hx-get="{reverse('break-point-create-form')}"
+ """
+
+ nav_title = _("Break Point Condition")
+ search_swap_target = "#listContainer"
+ filter_instance = AttendanceBreakpointFilter()
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(
+ permission_required("attendance.view_attendancevalidationcondition"),
+ name="dispatch",
+)
+class BreakPointCreateForm(HorillaFormView):
+ """
+ form view for create and edit Break Point in settings
+ """
+
+ model = AttendanceValidationCondition
+ form_class = AttendanceValidationConditionForm
+ new_display_title = _("Create Attendance condition")
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ # form = self.form_class()
+ if self.form.instance.pk:
+ form = self.form_class(instance=self.form.instance)
+ self.form_class.verbose_name = _("Update Attendance condition")
+ context[form] = self.form
+ return context
+
+ def form_invalid(self, form: Any) -> HttpResponse:
+ if self.form.instance.pk:
+ self.form_class.verbose_name = _("Update Attendance condition")
+ if not form.is_valid():
+ errors = form.errors.as_data()
+ return render(
+ self.request, self.template_name, {"form": form, "errors": errors}
+ )
+ return super().form_invalid(form)
+
+ def form_valid(self, form: AttendanceValidationConditionForm) -> HttpResponse:
+ if form.is_valid():
+ if form.instance.pk:
+ messages.success(
+ self.request, _("Attendance Break-point settings updated.")
+ )
+ else:
+ messages.success(
+ self.request, _("Attendance Break-point settings created.")
+ )
+ form.save()
+ return self.HttpResponse("")
+ return super().form_valid(form)
diff --git a/attendance/cbv/check_in_check_out.py b/attendance/cbv/check_in_check_out.py
new file mode 100644
index 000000000..6423ce7af
--- /dev/null
+++ b/attendance/cbv/check_in_check_out.py
@@ -0,0 +1,50 @@
+from typing import Any
+
+from django.urls import reverse
+from attendance.filters import AttendanceGeneralSettingFilter
+from attendance.models import AttendanceGeneralSetting
+from horilla_views.generic.cbv.views import HorillaListView,HorillaNavView
+from django.utils.translation import gettext_lazy as _
+
+class CheckInCheckOutListView(HorillaListView):
+ """
+ List view of the page
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.view_id = "check-in-check-out"
+
+ model = AttendanceGeneralSetting
+ filter_class = AttendanceGeneralSettingFilter
+
+ columns = [
+ (_("Company"), "company_col"),
+ (_("Check in/Check out"), "check_in_check_out_col"),
+ ]
+
+ header_attrs = {
+ "company_col" : """
+ style = "width:100px !important"
+ """,
+ }
+
+ bulk_select_option = False
+
+
+class CheckInCheckOutNavBar(HorillaNavView):
+ """
+ Nav bar
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ # self.search_url = reverse("check-in-check-out-list")
+
+
+ nav_title = _("Enable Check In/Check out")
+ filter_instance = AttendanceGeneralSettingFilter()
+ search_swap_target = "#listContainer"
+
+
+
\ No newline at end of file
diff --git a/attendance/cbv/dashboard.py b/attendance/cbv/dashboard.py
new file mode 100644
index 000000000..ea68397bc
--- /dev/null
+++ b/attendance/cbv/dashboard.py
@@ -0,0 +1,137 @@
+"""
+this page handles the cbv methods for dashboard
+"""
+
+from datetime import datetime
+from typing import Any
+from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
+from django.utils.decorators import method_decorator
+from attendance.cbv.attendances import OTAttendancesList, ValidateAttendancesList
+from attendance.filters import LateComeEarlyOutFilter
+from attendance.methods.utils import strtime_seconds
+from attendance.models import AttendanceLateComeEarlyOut, AttendanceValidationCondition
+from base.methods import filtersubordinates
+from horilla_views.cbv_methods import login_required
+from horilla_views.generic.cbv.views import HorillaListView
+
+
+
+@method_decorator(login_required, name="dispatch")
+class DashboardAttendanceToValidate(ValidateAttendancesList):
+ """
+ list view for attendance to validate in dashboard
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("dashboard-attendance-validate")
+ self.option_method = ""
+
+ columns = [
+ (_("Employee"), "employee_id", "employee_id__get_avatar"),
+ (_("Worked Hours"), "attendance_worked_hour"),
+
+ ]
+
+ header_attrs = {
+
+ "attendance_worked_hour": """
+ style="width:100px !important;"
+ """,
+ "employee_id": """
+ style="width:100px !important;"
+ """,
+ "action": """
+ style="width:100px !important;"
+ """,
+
+ }
+
+ records_per_page = 3
+ bulk_select_option = False
+ show_toggle_form = False
+
+
+@method_decorator(login_required, name="dispatch")
+class DashboardaAttendanceOT(OTAttendancesList):
+ """
+ list view for OT attendance to validate in dashboard
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("dashboard-overtime-approve")
+ self.option_method = ""
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ condition = AttendanceValidationCondition.objects.first()
+ minot = strtime_seconds("00:00")
+ if condition is not None and condition.minimum_overtime_to_approve is not None:
+ minot = strtime_seconds(condition.minimum_overtime_to_approve)
+ queryset = queryset.filter(
+ overtime_second__gte=minot,
+ attendance_validated=True,
+ employee_id__is_active=True,
+ attendance_overtime_approve=False,
+ )
+ queryset = filtersubordinates(
+ self.request, queryset, "attendance.view_attendance"
+ )
+ return queryset
+
+
+ columns = [
+ (_("Employee"), "employee_id", "employee_id__get_avatar"),
+ (_("Overtime"), "attendance_overtime"),
+ ]
+ header_attrs = {
+ "action": """
+ style="width:100px !important;"
+ """,
+ "attendance_overtime": """
+ style="width:100px !important;"
+ """,
+ "employee_id": """
+ style="width:100px !important;"
+ """,
+ }
+
+ show_toggle_form = False
+ records_per_page = 3
+ bulk_select_option = False
+
+
+
+
+@method_decorator(login_required, name="dispatch")
+class DashboardOnBreak(HorillaListView):
+ """
+ view for on break employee list
+ """
+
+ model = AttendanceLateComeEarlyOut
+ filter_class = LateComeEarlyOutFilter
+ show_toggle_form =False
+
+ bulk_select_option = False
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("dashboard-on-break")
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ today = datetime.today()
+ queryset = queryset.filter(
+ type="early_out", attendance_id__attendance_date=today
+ )
+ return queryset
+
+ columns = [
+ (_("Employee"), "employee_id", "employee_id__get_avatar"),
+ ]
+
+
+
\ No newline at end of file
diff --git a/attendance/cbv/dashboard_offline_online.py b/attendance/cbv/dashboard_offline_online.py
new file mode 100644
index 000000000..57e32b041
--- /dev/null
+++ b/attendance/cbv/dashboard_offline_online.py
@@ -0,0 +1,97 @@
+"""
+this page handles the cbv methods for online and offline employee list in dashboard
+"""
+
+from datetime import date
+from typing import Any
+from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
+from django.utils.decorators import method_decorator
+from attendance.cbv.attendances import OTAttendancesList, ValidateAttendancesList
+
+from base.decorators import manager_can_enter
+from employee.filters import EmployeeFilter
+from employee.models import Employee
+from horilla_views.cbv_methods import login_required
+from horilla_views.generic.cbv.views import HorillaListView
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("leave.view_leaverequest"), name="dispatch")
+class DashboardOfflineEmployees(HorillaListView):
+ """
+ list view for offline employees in dashboard
+ """
+
+ model = Employee
+ filter_class = EmployeeFilter
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("not-in-yet")
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ queryset = (
+ EmployeeFilter({"not_in_yet": date.today()})
+ .qs.exclude(employee_work_info__isnull=True)
+ .filter(is_active=True)
+ )
+
+ return queryset
+
+ columns = [
+ ("Employee", "get_full_name", "get_avatar"),
+ ("Work Status", "get_leave_status"),
+ ("Actions", "send_mail_button"),
+ ]
+ header_attrs = {
+ "get_full_name": """
+ style="width:200px !important;"
+ """,
+ "send_mail_button": """
+ style="width:80px !important;"
+ """,
+ }
+ records_per_page = 7
+ show_toggle_form = False
+ bulk_select_option = False
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("leave.view_leaverequest"), name="dispatch")
+class DashboardOnlineEmployees(HorillaListView):
+ """
+ list view for online employees in dashboard
+ """
+
+ model = Employee
+ filter_class = EmployeeFilter
+ show_toggle_form = False
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("not-out-yet")
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ queryset = (
+ EmployeeFilter({"not_out_yet": date.today()})
+ .qs.exclude(employee_work_info__isnull=True)
+ .filter(is_active=True)
+ )
+
+ return queryset
+
+ columns = [
+ ("Employee", "employee_id__get_full_name", "employee_id__get_avatar"),
+ ("Work Status", "get_custom_forecasted_info_col"),
+ ]
+
+ header_attrs = {
+ "employee_id__get_full_name": """ style="width:200px !important" """,
+ "get_custom_forecasted_info_col": """ style="width:180px !important" """,
+ }
+
+ records_per_page = 8
+ bulk_select_option = False
diff --git a/attendance/cbv/grace_time.py b/attendance/cbv/grace_time.py
new file mode 100644
index 000000000..e4f601069
--- /dev/null
+++ b/attendance/cbv/grace_time.py
@@ -0,0 +1,207 @@
+"""
+This page handles grace time in settings page.
+"""
+from typing import Any
+from django.http import HttpResponse
+from django.shortcuts import render
+from django.utils.translation import gettext_lazy as _
+from django.utils.decorators import method_decorator
+from django.urls import reverse
+from django.contrib import messages
+from attendance.filters import GraceTimeFilter
+from attendance.forms import GraceTimeForm
+from attendance.models import GraceTime
+from base.cbv.employee_shift import EmployeeShiftListView
+from horilla_views.cbv_methods import login_required, permission_required
+from horilla_views.generic.cbv.views import (
+ HorillaFormView,
+ HorillaListView,
+ HorillaNavView,
+)
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(
+ permission_required(perm="attendance.view_attendancevalidationcondition"),
+ name="dispatch",
+)
+class GenericGraceTimeListView(HorillaListView):
+ """
+ List view of the page
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.view_id = "all-container"
+
+ model = GraceTime
+ filter_class = GraceTimeFilter
+
+ columns = [
+ (_("Allowed Time"), "allowed_time_col"),
+ (_("Is active"), "is_active_col"),
+ (_("Applicable on clock-in"), "applicable_on_clock_in_col"),
+ (_("Applicable on clock-out"), "applicable_on_clock_out_col"),
+ (_("Assigned Shifts"), "get_shifts_display"),
+
+ ]
+
+ header_attrs = {
+ "allowed_time_col" : """
+ style = "width:200px !important"
+ """,
+
+ }
+
+ row_attrs = """
+ id = "graceTimeTr{get_instance_id}"
+ """
+
+ action_method = "action_col"
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(
+ permission_required(perm="attendance.view_attendancevalidationcondition"),
+ name="dispatch",
+)
+class DefaultGraceTimeList(GenericGraceTimeListView):
+ """
+ List of default grace time
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.view_id = "default-container"
+
+ selected_instances_key_id = "selectedInstancesDefault"
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ return queryset.filter(is_default=True)
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(
+ permission_required(perm="attendance.view_attendancevalidationcondition"),
+ name="dispatch",
+)
+class GraceTimeList(GenericGraceTimeListView):
+ """
+ List of grace time
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.view_id = "gracetime-container"
+
+ selected_instances_key_id = "selectedInstancesData"
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ return queryset.exclude(is_default=True)
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(
+ permission_required(perm="attendance.view_attendancevalidationcondition"),
+ name="dispatch",
+)
+class DefaultGraceTimeNav(HorillaNavView):
+ """
+ Nav bar
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ # self.search_url = reverse("grace-time-list")
+ default_grace_time = GraceTime.objects.filter(is_default=True).first()
+ if not default_grace_time and self.request.user.has_perm("attendance.add_gracetime"):
+ self.create_attrs = f"""
+ onclick = "event.stopPropagation();"
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ hx-target="#genericModalBody"
+ hx-get="{reverse('grace-time-create')}?default=True"
+ """
+
+ nav_title = _("Default Grace Time")
+ filter_instance = GraceTimeFilter()
+ search_swap_target = "#listContainer"
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(
+ permission_required(perm="attendance.view_attendancevalidationcondition"),
+ name="dispatch",
+)
+class GraceTimeNav(HorillaNavView):
+ """
+ Nav bar
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+
+ # self.search_url = reverse("grace-time-list")
+ self.create_attrs = f"""
+ onclick = "event.stopPropagation();"
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ hx-target="#genericModalBody"
+ hx-get="{reverse('grace-time-create')}?default=False"
+ """
+
+ nav_title = _("Grace Time")
+ filter_instance = GraceTimeFilter()
+ search_swap_target = "#listContainer"
+
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(permission_required(perm="attendance.add_gracetime"), name="dispatch")
+class GraceTimeFormView(HorillaFormView):
+ """
+ Create and edit form
+ """
+
+ model = GraceTime
+ form_class = GraceTimeForm
+ new_display_title = _("Create grace time")
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ is_default = eval(self.request.GET.get("default"))
+ self.form.fields["is_default"].initial = is_default
+ if self.form.instance.pk:
+ self.form.fields["shifts"].initial=self.form.instance.employee_shift.all()
+ self.form_class.verbose_name = _("Update grace time")
+ return context
+
+ def form_valid(self, form: GraceTimeForm) -> HttpResponse:
+ if form.is_valid():
+ gracetime = form.save()
+ if form.instance.pk:
+ gracetime.employee_shift.clear()
+ message = _("Grace time updated successfully.")
+ messages.success(self.request, message)
+ else:
+
+ message = _("Grace time created successfully.")
+ messages.success(self.request, message)
+ shifts = form.cleaned_data.get('shifts')
+ for shift in shifts:
+ shift.grace_time_id = gracetime
+ shift.save()
+ # form.save()
+ defaultValue = self.request.GET.get("default")
+ if defaultValue == "False":
+ return HttpResponse("")
+ return HttpResponse("")
+ return super().form_valid(form)
+
+
+
+EmployeeShiftListView.columns.append((_("Grace Time"), "get_grace_time"))
+EmployeeShiftListView.sortby_mapping.append(("Grace Time", "grace_time_id"))
+EmployeeShiftListView.bulk_update_fields.append("grace_time_id")
+
diff --git a/attendance/cbv/hour_account.py b/attendance/cbv/hour_account.py
new file mode 100644
index 000000000..e644d0bc9
--- /dev/null
+++ b/attendance/cbv/hour_account.py
@@ -0,0 +1,261 @@
+"""
+Hour account page
+"""
+
+from typing import Any
+from django.contrib import messages
+from django.http import HttpResponse
+from django.shortcuts import render
+from django.urls import reverse, reverse_lazy
+from django.utils.decorators import method_decorator
+from django.utils.translation import gettext_lazy as _
+from attendance.filters import AttendanceOverTimeFilter
+from attendance.forms import AttendanceOverTimeExportForm, AttendanceOverTimeForm
+from attendance.models import AttendanceOverTime
+from base.decorators import manager_can_enter
+from base.methods import choosesubordinates, filtersubordinates, is_reportingmanager
+from horilla_views.cbv_methods import login_required
+from horilla_views.generic.cbv.views import (
+ HorillaDetailedView,
+ HorillaFormView,
+ HorillaListView,
+ HorillaNavView,
+ TemplateView,
+)
+
+
+@method_decorator(login_required, name="dispatch")
+class HourAccount(TemplateView):
+ """
+ Hour Account
+ """
+
+ template_name = "cbv/hour_account/hour_account.html"
+
+@method_decorator(login_required, name="dispatch")
+class HourAccountList(HorillaListView):
+ """
+ List view
+ """
+
+ model = AttendanceOverTime
+ filter_class = AttendanceOverTimeFilter
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("attendance-ot-search")
+ self.view_id = "ot-table"
+
+ if self.request.user.has_perm(
+ "attendance.add_attendanceovertime"
+ ):
+ self.action_method = "hour_actions"
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ data = queryset
+ queryset = queryset.filter(employee_id__employee_user_id=self.request.user)
+ accounts = filtersubordinates(
+ self.request, data, "attendance.view_attendanceovertime"
+ )
+ return queryset | accounts
+
+ columns = [
+ (_("Employee"), "employee_id", "employee_id__get_avatar"),
+ (_("Month"), "get_month_capitalized"),
+ (_("Year"), "year"),
+ (_("Worked Hours"), "worked_hours"),
+ (_("Hours to Validate"), "not_validated_hrs"),
+ (_("Pending Hours"), "pending_hours"),
+ (_("Overtime Hours"), "overtime"),
+ (_("Not Approved OT Hours"), "not_approved_ot_hrs"),
+ ]
+
+ header_attrs = {
+ "employee_id" : """
+ style='width:200px !important'
+ """,
+ "not_approved_ot_hrs" : """
+ style='width:180px !important'
+ """,
+ "action" : """
+ style="width:160px !important"
+ """
+ }
+
+ row_attrs = """
+ hx-get='{hour_account_detail}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+ sortby_mapping = [
+ ("Employee", "employee_id__get_full_name", "employee_id__get_avatar"),
+ ("Month", "get_month_capitalized"),
+ ("Year", "year"),
+ ("Worked Hours", "worked_hours"),
+ ("Overtime Hours", "overtime"),
+ ]
+ records_per_page = 20
+
+@method_decorator(login_required, name="dispatch")
+class HourAccountNav(HorillaNavView):
+ """
+ Nav bar
+ """
+
+ template_name = "cbv/hour_account/nav_hour_account.html"
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("attendance-ot-search")
+ if not self.request.user.has_perm(
+ "attendance.add_attendanceovertime"
+ ) and not is_reportingmanager(self.request):
+ self.create_attrs = None
+ else:
+ self.create_attrs = f"""
+ data-toggle="oh-modal-toggle"
+ data-target="#genericModal"
+ hx-target="#genericModalBody"
+ hx-get="{reverse_lazy('attendance-overtime-create')}"
+ """
+ actions = [
+ {
+ "action": _("Export"),
+ "attrs": f"""
+ data-toggle="oh-modal-toggle"
+ data-target="#hourAccountExport"
+ hx-get="{reverse('hour-account-export')}"
+ hx-target="#hourAccountExportModalBody"
+ style="cursor: pointer;"
+ """,
+ }
+ ]
+
+ if self.request.user.has_perm("attendance.add_attendanceovertime"):
+ actions.append(
+ {
+ "action": _("Delete"),
+ "attrs": """
+ onclick="
+ hourAccountbulkDelete();
+ "
+ data-action = "delete"
+ style="cursor: pointer; color:red !important"
+ """,
+ },
+ )
+
+ if not self.request.user.has_perm(
+ "attendance.add_attendanceovertime"
+ ) and not is_reportingmanager(self.request):
+ actions = None
+
+ self.actions = actions
+
+ nav_title = _("Hour Account")
+ filter_instance = AttendanceOverTimeFilter()
+ filter_body_template = "cbv/hour_account/hour_filter.html"
+ filter_form_context_name = "form"
+ search_swap_target = "#listContainer"
+
+ group_by_fields = [
+ ("employee_id", _("Employee")),
+ ("month", _("Month")),
+ ("year", _("Year")),
+ ("employee_id__country", _("Country")),
+ ("employee_id__employee_work_info__reporting_manager_id", _("Reporting Manager")),
+ ("employee_id__employee_work_info__shift_id", _("Shift")),
+ ("employee_id__employee_work_info__work_type_id", _("Work Type")),
+ ("employee_id__employee_work_info__department_id", _("Department")),
+ ("employee_id__employee_work_info__job_position_id", _("Job Position")),
+ ("employee_id__employee_work_info__employee_type_id", _("Employment Type")),
+ ("employee_id__employee_work_info__company_id", _("Company")),
+ ]
+
+@method_decorator(login_required, name="dispatch")
+class HourExportView(TemplateView):
+ """
+ For candidate export
+ """
+
+ template_name = "cbv/hour_account/hour_export.html"
+
+ def get_context_data(self, **kwargs: Any):
+ context = super().get_context_data(**kwargs)
+ attendances = AttendanceOverTime.objects.all()
+ export_fields = AttendanceOverTimeExportForm
+ export_obj = AttendanceOverTimeFilter(queryset=attendances)
+ context["export_fields"] = export_fields
+ context["export_obj"] = export_obj
+ return context
+
+@method_decorator(login_required, name="dispatch")
+class HourAccountDetailView(HorillaDetailedView):
+ """
+ Detail View
+ """
+
+ model = AttendanceOverTime
+ title = _("Details")
+
+ header = {
+ "title": "employee_id__get_full_name",
+ "subtitle": "hour_account_subtitle",
+ "avatar": "employee_id__get_avatar",
+ }
+
+ body = [
+ (_("Month"), "get_month_capitalized"),
+ (_("Year"), "year"),
+ (_("Worked Hours"), "worked_hours"),
+ (_("Pending Hours"), "pending_hours"),
+ (_("Over time"), "overtime"),
+ ]
+
+ action_method = "hour_detail_actions"
+
+@method_decorator(login_required, name="dispatch")
+@method_decorator(manager_can_enter("attendance.add_attendanceovertime"),name='dispatch')
+class HourAccountFormView(HorillaFormView):
+ """
+ Form View
+ """
+
+ model = AttendanceOverTime
+ form_class = AttendanceOverTimeForm
+ # template_name = "cbv/recruitment/forms/create_form.html"
+ new_display_title = _("Hour Account")
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ self.form_class(initial={"employee_id": self.request.user.employee_get})
+ if self.form.instance.pk:
+ self.form_class.verbose_name = _("Hour account update")
+ self.form_class(instance=self.form.instance)
+ self.form = choosesubordinates(
+ self.request, self.form, "attendance.add_attendanceovertime"
+ )
+ context["form"] = self.form
+ return context
+
+ def form_invalid(self, form: Any) -> HttpResponse:
+ # form = self.form_class(self.request.POST)
+ if not form.is_valid():
+ errors = form.errors.as_data()
+ return render(
+ self.request, self.template_name, {"form": form, "errors": errors}
+ )
+ return super().form_invalid(form)
+
+ def form_valid(self, form: AttendanceOverTimeForm) -> HttpResponse:
+ if form.is_valid():
+ if form.instance.pk:
+ message = _("Attendance account updated")
+ else:
+ message = _("Attendance account added")
+ form.save()
+ messages.success(self.request, _(message))
+ return self.HttpResponse()
+ return super().form_valid(form)
diff --git a/attendance/cbv/late_come_and_early_out.py b/attendance/cbv/late_come_and_early_out.py
new file mode 100644
index 000000000..eff1fb870
--- /dev/null
+++ b/attendance/cbv/late_come_and_early_out.py
@@ -0,0 +1,236 @@
+"""
+Late come and early out page
+"""
+from typing import Any
+from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
+from django.utils.decorators import method_decorator
+from attendance.filters import LateComeEarlyOutFilter
+from attendance.forms import LateComeEarlyOutExportForm
+from attendance.models import AttendanceLateComeEarlyOut
+from base.filters import PenaltyFilter
+from base.methods import filtersubordinates, is_reportingmanager
+from base.models import PenaltyAccounts
+from horilla_views.cbv_methods import login_required
+from horilla_views.generic.cbv.views import (
+ HorillaListView,
+ HorillaNavView,
+ TemplateView,
+ HorillaDetailedView,
+)
+
+@method_decorator(login_required, name="dispatch")
+class LateComeAndEarlyOut(TemplateView):
+ """
+ Late come and early out
+ """
+
+ template_name = "cbv/late_come_and_early_out/late_come_and_early_out.html"
+
+@method_decorator(login_required, name="dispatch")
+class LateComeAndEarlyOutList(HorillaListView):
+ """
+ List view
+ """
+
+ filter_keys_to_remove = [
+ "late_early_id"
+ ]
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("late-come-early-out-search")
+ self.view_id = "late-container"
+ if (
+ not self.request.user.has_perm("attendance.chanage_penaltyaccount")
+ and not is_reportingmanager(self.request)
+ and not self.request.user.has_perm(
+ "perms.attendance.delete_attendancelatecomeearlyout"
+ )
+ ):
+ self.action_method = None
+ else:
+ self.action_method = "actions_column"
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ reports = queryset
+ self_reports = queryset.filter(employee_id__employee_user_id=self.request.user)
+ reports = filtersubordinates(
+ self.request, reports, "attendance.view_attendancelatecomeearlyout"
+ )
+ queryset = self_reports | reports
+ return queryset
+
+ row_attrs = """
+ hx-get='{late_come_detail}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+
+ model = AttendanceLateComeEarlyOut
+ filter_class = LateComeEarlyOutFilter
+ columns = [
+ (_("Employee"), "employee_id", "employee_id__get_avatar"),
+ (_("Type"), "get_type"),
+ (_("Attendance Date"), "attendance_id__attendance_date"),
+ (_("Check-In"), "attendance_id__attendance_clock_in"),
+ (_("In Date"), "attendance_id__attendance_clock_in_date"),
+ (_("Check-Out"), "attendance_id__attendance_clock_out"),
+ (_("Out Date"), "attendance_id__attendance_clock_out_date"),
+ (_("Min Hour"), "attendance_id__minimum_hour"),
+ (_("At Work"), "attendance_id__attendance_worked_hour"),
+ (_("Penalities"), "penalities_column"),
+ ]
+
+ header_attrs = {
+ "penalities_column" :"""
+ style ="width:170px !important"
+ """
+ }
+
+ sortby_mapping = [
+ ("Employee", "employee_id__get_full_name", "employee_id__get_avatar"),
+ ("Type", "get_type"),
+ ("Attendance Date", "attendance_id__attendance_date"),
+ ("Check-In", "attendance_id__attendance_clock_in"),
+ ("In Date", "attendance_id__attendance_clock_in_date"),
+ ("Check-Out", "attendance_id__attendance_clock_out"),
+ ("Out Date", "attendance_id__attendance_clock_out_date"),
+ ("At Work", "attendance_id__attendance_worked_hour"),
+ ("Min Hour", "attendance_id__minimum_hour"),
+
+ ]
+
+@method_decorator(login_required, name="dispatch")
+class LateComeAndEarlyOutListNav(HorillaNavView):
+ """
+ Nav bar
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("late-come-early-out-search")
+ actions = [
+ {
+ "action": _("Export"),
+ "attrs": f"""
+ data-toggle="oh-modal-toggle"
+ data-target="#attendanceExport"
+ hx-get="{reverse('late-come-and-early-out-export')}"
+ hx-target="#attendanceExportForm"
+ style="cursor: pointer;"
+ """,
+ }
+ ]
+
+ if self.request.user.has_perm(
+ "attendance.chanage_penaltyaccount"
+ ) or self.request.user.has_perm(
+ "perms.attendance.delete_attendancelatecomeearlyout"
+ ):
+ actions.append(
+ {
+ "action": _("Delete"),
+ "attrs": """
+ onclick="
+ lateComeBulkDelete();
+ "
+ data-action = "delete"
+ style="cursor: pointer; color:red !important"
+ """,
+ },
+ )
+
+ if (
+ not self.request.user.has_perm("attendance.chanage_penaltyaccount")
+ and not is_reportingmanager(self.request)
+ and not self.request.user.has_perm(
+ "perms.attendance.delete_attendancelatecomeearlyout"
+ )
+ ):
+ actions = None
+
+ self.actions = actions
+
+ nav_title = _("Late Come/Early Out ")
+ filter_instance = LateComeEarlyOutFilter()
+ filter_body_template = "cbv/late_come_and_early_out/late_early_filter.html"
+ filter_form_context_name = "form"
+ search_swap_target = "#listContainer"
+
+ group_by_fields = [
+ ("employee_id", _("Employee")),
+ ("type", _("Type")),
+ ("attendance_id__attendance_date", _("Attendance Date")),
+ ("attendance_id__shift_id", _("Shift")),
+ ("attendance_id__work_type_id", _("Work Type")),
+ ("attendance_id__minimum_hour", _("Minimum Hour")),
+ ("attendance_id__employee_id__country", _("Country")),
+ (
+ "attendance_id__employee_id__employee_work_info__reporting_manager_id",
+ _("Reporting Manager"),
+ ),
+ ("attendance_id__employee_id__employee_work_info__department_id", _("Department")),
+ (
+ "attendance_id__employee_id__employee_work_info__job_position_id",
+ _("Job Position"),
+ ),
+ (
+ "attendance_id__employee_id__employee_work_info__employee_type_id",
+ _("Employment Type"),
+ ),
+ ("attendance_id__employee_id__employee_work_info__company_id", _("Company")),
+ ]
+
+
+@method_decorator(login_required, name="dispatch")
+class LateEarlyExportView(TemplateView):
+ """
+ For export records
+ """
+
+ template_name = "cbv/late_come_and_early_out/late_early_export.html"
+
+ def get_context_data(self, **kwargs: Any):
+ context = super().get_context_data(**kwargs)
+ data = AttendanceLateComeEarlyOut.objects.all()
+ export_form = LateComeEarlyOutExportForm
+ export = LateComeEarlyOutFilter(queryset=data)
+ context["export_form"] = export_form
+ context["export"] = export
+ return context
+
+@method_decorator(login_required, name="dispatch")
+class LateComeEarlyOutDetailView(HorillaDetailedView):
+ """
+ Detail View
+ """
+
+ model = AttendanceLateComeEarlyOut
+ title = _("Details")
+
+ header = {
+ "title": "employee_id__get_full_name",
+ "subtitle": "late_come_subtitle",
+ "avatar": "employee_id__get_avatar",
+ }
+
+ body = [
+ (_("Type"), "get_type"),
+ (_("Attendance Date"), "attendance_id__attendance_date"),
+ (_("Check-In"), "attendance_id__attendance_clock_in"),
+ (_("Chen-in Date"), "attendance_id__attendance_clock_in_date"),
+ (_("Check-Out"), "attendance_id__attendance_clock_out"),
+ (_("Check-out Date"), "attendance_id__attendance_clock_out_date"),
+ (_("Min Hour"), "attendance_id__minimum_hour"),
+ (_("At Work"), "attendance_id__attendance_worked_hour"),
+ (_("Shift"), "attendance_id__shift_id"),
+ (_("Work Type"), "attendance_id__work_type_id"),
+ (_("Attendance Validated"), "attendance_validated_check"),
+ (_("Penalities"), "penalities_column"),
+ ]
+
+ action_method = "detail_actions"
+
diff --git a/attendance/cbv/my_attendances.py b/attendance/cbv/my_attendances.py
new file mode 100644
index 000000000..f699fd768
--- /dev/null
+++ b/attendance/cbv/my_attendances.py
@@ -0,0 +1,202 @@
+"""
+My attendances
+"""
+
+from typing import Any
+
+from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
+from django.utils.decorators import method_decorator
+from attendance.filters import AttendanceFilters
+from attendance.models import Attendance
+from horilla_views.cbv_methods import login_required
+from horilla_views.generic.cbv.views import (
+ HorillaListView,
+ HorillaNavView,
+ TemplateView,
+ HorillaDetailedView,
+)
+
+
+@method_decorator(login_required, name="dispatch")
+class MyAttendances(TemplateView):
+ """
+ My attendances
+ """
+
+ template_name = "cbv/my_attendances/my_attendances.html"
+
+class MyAttendancesListView(HorillaListView):
+
+
+
+ model = Attendance
+ filter_class = AttendanceFilters
+ columns = [
+ (_("Employee"), "employee_id", "employee_id__get_avatar"),
+ (_("Date"), "attendance_date"),
+ (_("Day"), "attendance_day"),
+ (_("Check-In"), "attendance_clock_in"),
+ (_("In Date"), "attendance_clock_in_date"),
+ (_("Check-Out"), "attendance_clock_out"),
+ (_("Out Date"), "attendance_clock_out_date"),
+ (_("Shift"), "shift_id"),
+ (_("Work Type"), "work_type_id"),
+ (_("Min Hour"), "minimum_hour"),
+ (_("At Work"), "attendance_worked_hour"),
+ (_("Pending Hour"), "hours_pending"),
+ (_("Overtime"), "attendance_overtime"),
+ ]
+
+ row_attrs = """
+ hx-get='{my_attendance_detail}?instance_ids={ordered_ids}'
+ hx-target="#genericModalBody"
+ data-target="#genericModal"
+ data-toggle="oh-modal-toggle"
+ """
+ records_per_page = 20
+
+ sortby_mapping = [
+ ("Employee", "employee_id__get_full_name", "employee_id__get_avatar"),
+ ("Date", "attendance_date"),
+ ("Day", "attendance_day__day"),
+ ("Check-In", "attendance_clock_in"),
+ ("Shift", "shift_id__employee_shift"),
+ ("Work Type", "work_type_id__work_type"),
+ ("Min Hour", "minimum_hour"),
+ ("Pending Hour", "hours_pending"),
+ ("In Date", "attendance_clock_in_date"),
+ ("Check-Out", "attendance_clock_out"),
+ ("Out Date", "attendance_clock_out_date"),
+ ("At Work", "attendance_worked_hour"),
+ ("Overtime", "attendance_overtime"),
+ ]
+
+
+
+@method_decorator(login_required, name="dispatch")
+class MyAttendanceList(MyAttendancesListView):
+ """
+ List view
+ """
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("my-attendance-list")
+
+ row_status_indications = [
+ (
+ "approved-request--dot",
+ _("Approved Request"),
+ """
+ onclick="
+ $('#applyFilter').closest('form').find('[name=is_validate_request_approved]').val('true');
+ $('[name=attendance_validated]').val('unknown').change();
+ $('[name=is_validate_request]').val('unknown').change();
+ $('#applyFilter').click();
+
+ "
+ """,
+ ),
+ (
+ "requested--dot",
+ _("Requested"),
+ """
+ onclick="
+ $('#applyFilter').closest('form').find('[name=is_validate_request]').val('true');
+ $('[name=attendance_validated]').val('unknown').change();
+ $('[name=is_validate_request_approved]').val('unknown').change();
+ $('#applyFilter').click();
+
+ "
+ """,
+ ),
+ (
+ "not-validated--dot",
+ _("Not Validated"),
+ """
+ onclick="
+ $('#applyFilter').closest('form').find('[name=attendance_validated]').val('false');
+ $('[name=is_validate_request]').val('unknown').change();
+ $('[name=is_validate_request_approved]').val('unknown').change();
+ $('#applyFilter').click();
+ "
+ """,
+ ),
+ (
+ "validated--dot",
+ _("Validated"),
+ """
+ onclick="
+ $('#applyFilter').closest('form').find('[name=attendance_validated]').val('true');
+ $('[name=is_validate_request]').val('unknown').change();
+ $('[name=is_validate_request_approved]').val('unknown').change();
+ $('#applyFilter').click();
+
+ "
+ """,
+ ),
+ ]
+
+ row_status_class = "validated-{attendance_validated} requested-{is_validate_request} approved-request-{is_validate_request_approved}"
+
+ def get_queryset(self):
+ queryset = super().get_queryset()
+ employee = self.request.user.employee_get
+ queryset = queryset.filter(employee_id=employee)
+ return queryset
+
+
+
+
+
+@method_decorator(login_required, name="dispatch")
+class MyAttendancestNav(HorillaNavView):
+ """
+ Nav bar
+ """
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.search_url = reverse("my-attendance-list")
+ self.search_in = [
+ ("shift_id__employee_shift", "Shift"),
+ ("work_type_id__work_type", "Work Type"),
+ ]
+
+ nav_title = _("My Attendances")
+ filter_body_template = "cbv/my_attendances/my_attendance_filter.html"
+ filter_instance = AttendanceFilters()
+ filter_form_context_name = "form"
+ search_swap_target = "#listContainer"
+
+
+@method_decorator(login_required, name="dispatch")
+class MyAttendancesDetailView(HorillaDetailedView):
+ """
+ Detail View
+ """
+
+ model = Attendance
+
+ title = _("Details")
+
+ header = {
+ "title": "employee_id__get_full_name",
+ "subtitle": "my_attendance_subtitle",
+ "avatar": "employee_id__get_avatar",
+ }
+
+ body = [
+ (_("Date"), "attendance_date"),
+ (_("Day"), "attendance_day"),
+ (_("Check-In"), "attendance_clock_in"),
+ (_("Check-in Date"), "attendance_clock_in_date"),
+ (_("Check-Out"), "attendance_clock_out"),
+ (_("Check-out Date"), "attendance_clock_out_date"),
+ (_("Shift"), "shift_id"),
+ (_("Work Type"), "work_type_id"),
+ (_("Min Hour"), "minimum_hour"),
+ (_("At Work"), "attendance_worked_hour"),
+ (_("Pending Hour"), "hours_pending"),
+ (_("Overtime"), "attendance_overtime"),
+ ]
diff --git a/attendance/filters.py b/attendance/filters.py
index f8d928f34..416e9ffed 100644
--- a/attendance/filters.py
+++ b/attendance/filters.py
@@ -16,14 +16,17 @@ from django.utils.translation import gettext_lazy as _
from attendance.models import (
Attendance,
AttendanceActivity,
+ AttendanceGeneralSetting,
AttendanceLateComeEarlyOut,
AttendanceOverTime,
+ AttendanceValidationCondition,
+ GraceTime,
strtime_seconds,
)
from base.filters import FilterSet
from employee.filters import EmployeeFilter
from employee.models import Employee
-from horilla.filters import filter_by_name
+from horilla.filters import HorillaFilterSet, filter_by_name
class DurationInSecondsFilter(django_filters.CharFilter):
@@ -50,7 +53,7 @@ class DurationInSecondsFilter(django_filters.CharFilter):
return qs
-class AttendanceOverTimeFilter(FilterSet):
+class AttendanceOverTimeFilter(HorillaFilterSet):
"""
Filter set class for AttendanceOverTime model
@@ -126,7 +129,7 @@ class AttendanceOverTimeFilter(FilterSet):
self.form.fields[field].widget.attrs["id"] = f"{uuid.uuid4()}"
-class LateComeEarlyOutFilter(FilterSet):
+class LateComeEarlyOutFilter(HorillaFilterSet):
"""
LateComeEarlyOutFilter class
"""
@@ -246,7 +249,7 @@ class LateComeEarlyOutFilter(FilterSet):
self.form.fields[field].widget.attrs["id"] = f"{uuid.uuid4()}"
-class AttendanceActivityFilter(FilterSet):
+class AttendanceActivityFilter(HorillaFilterSet):
"""
Filter set class for AttendanceActivity model
@@ -329,7 +332,7 @@ class AttendanceActivityFilter(FilterSet):
self.form.fields[field].widget.attrs["id"] = f"{uuid.uuid4()}"
-class AttendanceFilters(FilterSet):
+class AttendanceFilters(HorillaFilterSet):
"""
Filter set class for Attendance model
@@ -339,6 +342,8 @@ class AttendanceFilters(FilterSet):
id = django_filters.NumberFilter(field_name="id")
search = django_filters.CharFilter(method="filter_by_name")
+ search_field = django_filters.CharFilter(method="search_in")
+
employee = django_filters.CharFilter(field_name="employee_id__id")
date_attendance = django_filters.DateFilter(field_name="attendance_date")
employee_id = django_filters.ModelMultipleChoiceFilter(
@@ -489,6 +494,9 @@ class AttendanceFilters(FilterSet):
self.form.fields[field].widget.attrs["id"] = f"{uuid.uuid4()}"
def filter_by_name(self, queryset, name, value):
+
+ if self.data.get("search_field"):
+ return queryset
# Call the imported function
"""
This method allows filtering by the employee's first and/or last name or by other
@@ -651,6 +659,54 @@ class AttendanceRequestReGroup:
]
+class AttendanceBreakpointFilter(FilterSet):
+ """
+ filter class for attendance breakpoint condition model
+ """
+
+ search = django_filters.CharFilter(field_name="company_id", lookup_expr="icontains")
+
+ class Meta:
+ model = AttendanceValidationCondition
+ fields = [
+ "validation_at_work",
+ "minimum_overtime_to_approve",
+ "overtime_cutoff",
+ "company_id",
+ ]
+
+
+class GraceTimeFilter(HorillaFilterSet):
+
+ search = django_filters.CharFilter(method="search_method")
+
+ class Meta:
+ model = GraceTime
+ fields = ["company_id"]
+
+ def search_method(self, queryset, _, value):
+ """
+ This method is used to mail server
+ """
+
+ return ((queryset.filter(company_id__company__icontains=value))).distinct()
+
+
+class AttendanceGeneralSettingFilter(HorillaFilterSet):
+
+ search = django_filters.CharFilter(method="search_method")
+
+ class Meta:
+ model = AttendanceGeneralSetting
+ fields = ["company_id"]
+
+ def search_method(self, queryset, _, value):
+ """
+ This method is used to mail server
+ """
+ return ((queryset.filter(company_id__company__icontains=value))).distinct()
+
+
def get_working_today(queryset, _name, value):
today = datetime.datetime.now().date()
yesterday = today - datetime.timedelta(days=1)
diff --git a/attendance/forms.py b/attendance/forms.py
index 3b5c69362..edf124a71 100644
--- a/attendance/forms.py
+++ b/attendance/forms.py
@@ -37,6 +37,7 @@ 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.text import capfirst
from django.utils.translation import gettext_lazy as _
from attendance.filters import AttendanceFilters
@@ -55,6 +56,7 @@ from attendance.models import (
validate_time_format,
)
from base.forms import ModelForm as BaseModelForm
+from base.forms import MultipleFileField
from base.methods import (
filtersubordinatesemployeemodel,
get_working_days,
@@ -151,7 +153,8 @@ class AttendanceUpdateForm(BaseModelForm):
{
"id": str(uuid.uuid4()),
"hx-include": "#attendanceUpdateForm",
- "hx-target": "#attendanceUpdateForm",
+ "hx-target": "#attendanceUpdateFormFields,#personal",
+ "hx-trigger": "change",
"hx-get": "/attendance/update-fields-based-shift",
}
)
@@ -302,7 +305,8 @@ class AttendanceForm(BaseModelForm):
{
"id": str(uuid.uuid4()),
"hx-include": "#attendanceCreateForm",
- "hx-target": "#attendanceCreateForm",
+ "hx-target": "#attendanceFormFields,#personal",
+ "hx-trigger": "change",
"hx-get": "/attendance/update-fields-based-shift",
}
)
@@ -509,6 +513,13 @@ class AttendanceValidationConditionForm(forms.ModelForm):
Model form for AttendanceValidationCondition
"""
+ cols = {
+ "validation_at_work": 12,
+ "minimum_overtime_to_approve": 12,
+ "overtime_cutoff": 12,
+ "company_id": 12,
+ }
+
validation_at_work = forms.CharField(
required=True,
initial="00:00",
@@ -560,6 +571,8 @@ class AttendanceRequestForm(BaseModelForm):
AttendanceRequestForm
"""
+ cols = {"request_description": 12}
+
def update_worked_hour_hx_fields(self, field_name):
"""Update the widget attributes for worked hour fields."""
self.fields[field_name].widget.attrs.update(
@@ -576,7 +589,7 @@ class AttendanceRequestForm(BaseModelForm):
def __init__(self, *args, **kwargs):
if instance := kwargs.get("instance"):
- # django forms not showing value inside the date, time html element.
+ # django forms not showing vaupdate-fields-based-shiftlue 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"),
@@ -596,15 +609,16 @@ class AttendanceRequestForm(BaseModelForm):
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()),
- "hx-include": "#attendanceRequestForm",
- "hx-target": "#attendanceRequestDiv",
- "hx-swap": "outerHTML",
- "hx-get": "/attendance/update-fields-based-shift",
- }
- )
+ if not self.instance.pk:
+ self.fields["shift_id"].widget.attrs.update(
+ {
+ "id": str(uuid.uuid4()),
+ "hx-include": "#attendanceRequestForm",
+ "hx-target": "#attendanceRequest",
+ "hx-swap": "innerHTML",
+ "hx-get": "/attendance/update-fields-based-shift",
+ }
+ )
for field in [
"attendance_clock_in_date",
"attendance_clock_in",
@@ -679,7 +693,8 @@ class NewRequestForm(AttendanceRequestForm):
widget=forms.Select(
attrs={
"class": "oh-select oh-select-2 w-100",
- "hx-target": "#id_shift_id_div",
+ "hx-target": "#id_shift_id_parent_div,#id_shift_id_div",
+ "hx-swap": "innerHTML",
"hx-get": "/attendance/get-employee-shift?bulk=False",
}
),
@@ -691,15 +706,15 @@ class NewRequestForm(AttendanceRequestForm):
widget=forms.CheckboxInput(
attrs={
"class": "oh-checkbox",
- "hx-target": "#objectCreateModalTarget",
- "hx-get": "/attendance/request-new-attendance?bulk=True",
+ "hx-target": "#genericModalBody",
+ "hx-swap": "innerHTML",
+ "hx-get": "/attendance/request-bulk-attendance?bulk=True",
}
),
),
}
new_dict.update(old_dict)
self.fields = new_dict
-
kwargs["initial"] = view_initial
def as_p(self, *args, **kwargs):
@@ -918,6 +933,8 @@ class GraceTimeForm(BaseModelForm):
Form for create or update Grace time
"""
+ cols = {"allowed_time": 12, "company_id": 12, "shifts": 12}
+
shifts = forms.ModelMultipleChoiceField(
queryset=EmployeeShift.objects.all(),
required=False,
@@ -1036,7 +1053,8 @@ class BulkAttendanceRequestForm(BaseModelForm):
queryset=Employee.objects.filter(is_active=True),
widget=forms.Select(
attrs={
- "hx-target": "#id_shift_id_div",
+ "hx-target": "#id_shift_id_parent_div",
+ "hx-swap": "innerHTML",
"hx-get": "/attendance/get-employee-shift?bulk=True",
}
),
@@ -1049,7 +1067,8 @@ class BulkAttendanceRequestForm(BaseModelForm):
widget=forms.CheckboxInput(
attrs={
"class": "oh-checkbox",
- "hx-target": "#objectCreateModalTarget",
+ "hx-target": "#genericModalBody",
+ "hx-swap": "innerHTML",
"hx-get": "/attendance/request-new-attendance?bulk=False",
}
),
diff --git a/attendance/methods/utils.py b/attendance/methods/utils.py
index 5c19e2ee2..814d35776 100644
--- a/attendance/methods/utils.py
+++ b/attendance/methods/utils.py
@@ -551,6 +551,14 @@ def parse_time(time_str):
return None
+def parse_datetime(date_str, time_str):
+ return (
+ datetime.strptime(f"{date_str} {time_str}", "%Y-%m-%d %H:%M")
+ if date_str and time_str
+ else None
+ )
+
+
def parse_date(date_str, error_key, activity):
try:
return pd.to_datetime(date_str).date()
diff --git a/attendance/models.py b/attendance/models.py
index f16ff0d64..7946cfcca 100644
--- a/attendance/models.py
+++ b/attendance/models.py
@@ -10,10 +10,12 @@ import datetime as dt
import json
from datetime import date, datetime, timedelta
+import pandas as pd
from django.apps import apps
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Q
+from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
@@ -31,9 +33,12 @@ from base.horilla_company_manager import HorillaCompanyManager
from base.methods import is_company_leave, is_holiday
from base.models import Company, EmployeeShift, EmployeeShiftDay, WorkType
from employee.models import Employee
+
+# Create your models here.
from horilla.methods import get_horilla_model_class
from horilla.models import HorillaModel
from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog
+from horilla_views.cbv_methods import render_template
# to skip the migration issue with the old migrations
_validate_time_in_minutes = validate_time_in_minutes
@@ -81,6 +86,70 @@ class AttendanceActivity(HorillaModel):
ordering = ["-attendance_date", "employee_id__employee_first_name", "clock_in"]
+ def get_status(self):
+ """
+ Display status
+ """
+
+ DAY = [
+ ("monday", _("Monday")),
+ ("tuesday", _("Tuesday")),
+ ("wednesday", _("Wednesday")),
+ ("thursday", _("Thursday")),
+ ("friday", _("Friday")),
+ ("saturday", _("Saturday")),
+ ("sunday", _("Sunday")),
+ ]
+ return dict(DAY).get(self.shift_day.day)
+
+ def get_delete_attendance(self):
+ """
+ for delete button
+ """
+
+ return render_template(
+ path="cbv/attendance_activity/delete_action.html",
+ context={"instance": self},
+ )
+
+ def attendance_detail_subtitle(self):
+ """
+ Return subtitle containing both department and job position information.
+ """
+ return f"{self.employee_id.employee_work_info.department_id} / {self.employee_id.employee_work_info.job_position_id}"
+
+ def attendance_detail_view(self):
+ """
+ for detail view of page
+ """
+ url = reverse("attendance-activity-single-view", kwargs={"pk": self.pk})
+ return url
+
+ def diff_cell(self):
+ if self.clock_out == None:
+ return 'style="background-color: #FFE4B3"'
+
+ def detail_view_delete_attendance(self):
+ """
+ for delete button
+ """
+
+ return render_template(
+ path="cbv/attendance_activity/detail_delete_action.html",
+ context={"instance": self},
+ )
+
+ def duration_format_time(self, seconds):
+ """
+ This method is used to format seconds to H:M:S and return it
+ args:
+ seconds : seconds
+ """
+ hour = int(seconds // 3600)
+ minutes = int((seconds % 3600) // 60)
+ seconds = int((seconds % 3600) % 60)
+ return f"{hour:02d}:{minutes:02d}:{seconds:02d}"
+
def duration(self):
"""
Duration calc b/w in-out method
@@ -97,6 +166,15 @@ class AttendanceActivity(HorillaModel):
return time_difference.total_seconds()
+ def duration_format(self):
+ """
+ Function to return the duration time in hh:mm:ss
+ """
+ total_seconds = self.duration()
+ formatted_duration = self.duration_format_time(total_seconds)
+
+ return formatted_duration
+
def __str__(self):
return f"{self.employee_id} - {self.attendance_date} - {self.clock_in} - {self.clock_out}"
@@ -123,7 +201,7 @@ class Attendance(HorillaModel):
status = [
("create_request", _("Create Request")),
("update_request", _("Update Request")),
- ("revalidate_request", _("Re-validate Request")),
+ ("created_request", _("Created Request")),
]
employee_id = models.ForeignKey(
@@ -228,6 +306,49 @@ class Attendance(HorillaModel):
],
)
+ def get_instance_id(self):
+ return self.id
+
+ def diff_cell(self):
+ if self.request_type == "created_request":
+ return 'style="background-color: #FFE4B3"'
+
+ def status_col(self):
+ """
+ This method for get custome coloumn for rating.
+ """
+
+ return render_template(
+ path="cbv/attendance_request/status.html",
+ context={"instance": self},
+ )
+
+ def my_attendance_subtitle(self):
+ """
+ Detail view subtitle
+ """
+
+ return f"""{self.employee_id.employee_work_info.department_id } /
+ { self.employee_id.employee_work_info.job_position_id}"""
+
+ def my_attendance_detail(self):
+ """
+ detail view
+ """
+
+ url = reverse("my-attendance-detail", kwargs={"pk": self.pk})
+
+ return url
+
+ def attendance_detail_view(self):
+ """
+ detail view
+ """
+
+ url = reverse("attendances-tab-detail-view", kwargs={"pk": self.pk})
+
+ return url
+
class Meta:
"""
Meta class to add some additional options
@@ -276,6 +397,162 @@ class Attendance(HorillaModel):
)
return {"query": activities, "count": activities.count()}
+ def attendance_actions(self):
+ """
+ method for rendering actions(edit,delete)
+ """
+
+ return render_template(
+ path="cbv/attendances/attendance_actions.html",
+ context={"instance": self},
+ )
+
+ def comment_col(self):
+ """
+ This method for get custom coloumn for comment.
+ """
+
+ return render_template(
+ path="cbv/attendance_request/comment.html",
+ context={"instance": self},
+ )
+
+ def attendance_detail_activity_col(self):
+ """
+ this method is used to return attendance detail view activity custom col
+ """
+
+ return render_template(
+ path="cbv/attendances/detail_view_activity_col.html",
+ context={"instance": self},
+ )
+
+ def request_actions(self):
+ """
+ This method for get custom coloumn for comment.
+ """
+
+ return render_template(
+ path="cbv/attendance_request/request_actions.html",
+ context={"instance": self},
+ )
+
+ def validate_detail_view(self):
+ """
+ detail view of validate tab
+ """
+ url = reverse("validate-detail-view", kwargs={"pk": self.pk})
+ return url
+
+ def individual_validate_detail_view(self):
+ """
+ detail view of validate tab
+ """
+ url = reverse("individual-validate-detail-view", kwargs={"pk": self.pk})
+ return url
+
+ def ot_detail_view(self):
+ """
+ detail view of OT tab
+ """
+ url = reverse("ot-detail-view", kwargs={"pk": self.pk})
+ return url
+
+ def validated_detail_view(self):
+ """
+ detail view of validated tab
+ """
+ url = reverse("validated-detail-view", kwargs={"pk": self.pk})
+ return url
+
+ def detail_view(self):
+ """
+ deteil view of requested attendances
+ """
+ url = reverse("validate-attendance-request", kwargs={"attendance_id": self.pk})
+ return url
+
+ def change_attendance(self):
+ """
+ Edit url
+ """
+ url = reverse("update-attendance-request", kwargs={"pk": self.pk})
+ return url
+
+ def ot_approve(self):
+ """
+ method for rendering approve OT
+ """
+ minot = strtime_seconds("00:30")
+ condition = AttendanceValidationCondition.objects.first()
+ if condition is not None:
+ minot = strtime_seconds(condition.minimum_overtime_to_approve)
+
+ return render_template(
+ path="cbv/attendances/ot_confirmation.html",
+ context={"instance": self, "minot": minot},
+ )
+
+ def validate_detail_actions(self):
+ """
+ detail view actions of validate tab
+ """
+
+ return render_template(
+ path="cbv/attendances/validate_tab_action.html",
+ context={"instance": self},
+ )
+
+ def ot_detail_actions(self):
+ """
+ detail view actions of OT tab
+ """
+
+ minot = strtime_seconds("00:30")
+ condition = AttendanceValidationCondition.objects.first()
+ if condition is not None:
+ minot = strtime_seconds(condition.minimum_overtime_to_approve)
+
+ return render_template(
+ path="cbv/attendances/ot_tab_action.html",
+ context={"instance": self, "minot": minot},
+ )
+
+ def validated_detail_actions(self):
+ """
+ detail view actions of validated tab
+ """
+
+ return render_template(
+ path="cbv/attendances/validated_tab_action.html",
+ context={"instance": self},
+ )
+
+ def validate_button(self):
+ """
+ detail view actions of validated tab
+ """
+
+ return render_template(
+ path="cbv/attendances/validate_button.html",
+ context={"instance": self},
+ )
+
+ def attendances_detail_subtitle(self):
+ """
+ Return subtitle containing both department and job position information.
+ """
+ return f"{self.employee_id.employee_work_info.department_id} / {self.employee_id.employee_work_info.job_position_id}"
+
+ def activities(self):
+ """
+ This method is used to return the activites and count of activites comes for an attendance
+ """
+ activities = AttendanceActivity.objects.filter(
+ attendance_date=self.attendance_date, employee_id=self.employee_id
+ )
+ return {"query": activities, "count": activities.count()}
+
def requested_fields(self):
"""
This method will returns the value difference fields
@@ -419,6 +696,7 @@ class Attendance(HorillaModel):
attendance_account.overtime = format_time(total_ot_seconds)
attendance_account.save()
super().save(*args, **kwargs)
+ self.first_save = False
def serialize(self):
"""
@@ -663,6 +941,69 @@ class AttendanceOverTime(HorillaModel):
verbose_name = _("Hour Account")
verbose_name_plural = _("Hour Accounts")
+ def get_month_capitalized(self):
+ """
+ capitalize month
+ """
+ return self.month.capitalize()
+
+ def edit_url_overtime(self):
+ """
+ Edit url
+ """
+
+ url = reverse("attendance-overtime-update", kwargs={"obj_id": self.pk})
+
+ return url
+
+ def delete_url_overtime(self):
+ """
+ delete url
+ """
+
+ url = reverse("attendance-overtime-delete", kwargs={"obj_id": self.pk})
+
+ return url
+
+ def hour_actions(self):
+ """
+ actions in hour account
+
+ """
+
+ return render_template(
+ path="cbv/hour_account/hour_actions.html",
+ context={"instance": self},
+ )
+
+ def hour_account_subtitle(self):
+ """
+ Detail view subtitle
+ """
+
+ return f"""{self.employee_id.employee_work_info.department_id } /
+ { self.employee_id.employee_work_info.job_position_id}"""
+
+ def hour_account_detail(self):
+ """
+ detail view
+ """
+
+ url = reverse("hour-account-detail-view", kwargs={"pk": self.pk})
+
+ return url
+
+ def hour_detail_actions(self):
+ """
+ actions in hour account detail view
+
+ """
+
+ return render_template(
+ path="cbv/hour_account/hour_detail_action.html",
+ context={"instance": self},
+ )
+
def clean(self):
try:
year = int(self.year)
@@ -800,6 +1141,72 @@ class AttendanceLateComeEarlyOut(HorillaModel):
unique_together = [("attendance_id"), ("type")]
ordering = ["-attendance_id__attendance_date"]
+ def get_type(self):
+ """
+ Display work type
+ """
+ choices = [
+ ("late_come", _("Late Come")),
+ ("early_out", _("Early Out")),
+ ]
+ return dict(choices).get(self.type)
+
+ def penalities_column(self):
+ """
+ To get penalities
+
+ """
+
+ return render_template(
+ path="cbv/late_come_and_early_out/penality.html",
+ context={"instance": self},
+ )
+
+ def actions_column(self):
+ """
+ actions in hour account
+
+ """
+
+ return render_template(
+ path="cbv/late_come_and_early_out/actions_column.html",
+ context={"instance": self},
+ )
+
+ def detail_actions(self):
+ """
+ actions in hour account
+
+ """
+
+ return render_template(
+ path="cbv/late_come_and_early_out/detail_action.html",
+ context={"instance": self},
+ )
+
+ def late_come_subtitle(self):
+ """
+ Detail view subtitle
+ """
+
+ return f"""{self.employee_id.employee_work_info.department_id } /
+ { self.employee_id.employee_work_info.job_position_id}"""
+
+ def attendance_validated_check(self):
+ if self.attendance_id.attendance_validated == True:
+ return "Yes"
+ else:
+ return "No"
+
+ def late_come_detail(self):
+ """
+ detail view
+ """
+
+ url = reverse("late-in-early-out-single-view", kwargs={"pk": self.pk})
+
+ return url
+
def __str__(self) -> str:
return f"{self.attendance_id.employee_id.employee_first_name} \
{self.attendance_id.employee_id.employee_last_name} - {self.type}"
@@ -835,6 +1242,17 @@ class AttendanceValidationCondition(HorillaModel):
if not self.id and AttendanceValidationCondition.objects.exists():
raise ValidationError(_("You cannot add more conditions."))
+ def break_point_actions(self):
+ """
+ actions in hour account
+
+ """
+
+ return render_template(
+ path="cbv/settings/break_point_action.html",
+ context={"instance": self},
+ )
+
class GraceTime(HorillaModel):
"""
@@ -866,6 +1284,65 @@ class GraceTime(HorillaModel):
def __str__(self) -> str:
return str(f"{self.allowed_time} - Hours")
+ def get_instance_id(self):
+ return self.id
+
+ def is_active_col(self):
+ """
+ This method for get custome coloumn .
+ """
+
+ return render_template(
+ path="cbv/settings/is_active_col_grace_time.html",
+ context={"instance": self},
+ )
+
+ def allowed_time_col(self):
+ """
+ Allowed time col
+ """
+ return f"{self.allowed_time} Hours"
+
+ def action_col(self):
+ """
+ This method for get custome coloumn .
+ """
+
+ return render_template(
+ path="cbv/settings/grace_time_default_action.html",
+ context={"instance": self},
+ )
+
+ def applicable_on_clock_in_col(self):
+ """
+ This method for get custom column .
+ """
+
+ return render_template(
+ path="cbv/settings/applicable_on_clock_in_col.html",
+ context={"instance": self},
+ )
+
+ def applicable_on_clock_out_col(self):
+ """
+ This method for get custom column .
+ """
+
+ return render_template(
+ path="cbv/settings/applicable_on_clock_out_col.html",
+ context={"instance": self},
+ )
+
+ def get_shifts_display(self):
+ """
+ This method for get custom column .
+ """
+
+ return render_template(
+ path="cbv/settings/grace_time_shift.html",
+ context={"instance": self},
+ )
+
def clean(self):
"""
This method is used to perform some custom validations
@@ -928,6 +1405,22 @@ class AttendanceGeneralSetting(HorillaModel):
company_id = models.ForeignKey(Company, on_delete=models.CASCADE, null=True)
objects = HorillaCompanyManager()
+ def company_col(self):
+ if self.company_id:
+ return self.company_id.company
+ else:
+ return "All Company"
+
+ def check_in_check_out_col(self):
+ """
+ This method for get custom coloumn .
+ """
+
+ return render_template(
+ path="cbv/settings/check_in_check_out_col.html",
+ context={"instance": self},
+ )
+
class WorkRecords(models.Model):
"""
diff --git a/attendance/static/cbv/attendance/hour_account.js b/attendance/static/cbv/attendance/hour_account.js
new file mode 100644
index 000000000..e683b9796
--- /dev/null
+++ b/attendance/static/cbv/attendance/hour_account.js
@@ -0,0 +1,327 @@
+
+
+var downloadMessages = {
+ ar: "هل ترغب في تنزيل القالب؟",
+ de: "Möchten Sie die Vorlage herunterladen?",
+ es: "¿Quieres descargar la plantilla?",
+ en: "Do you want to download the template?",
+ fr: "Voulez-vous télécharger le modèle ?",
+ };
+ var validateMessages = {
+ ar: "هل ترغب حقًا في التحقق من كل الحضور المحدد؟",
+ de: "Möchten Sie wirklich alle ausgewählten Anwesenheiten überprüfen?",
+ es: "¿Realmente quieres validar todas las asistencias seleccionadas?",
+ en: "Do you really want to validate all the selected attendances?",
+ fr: "Voulez-vous vraiment valider toutes les présences sélectionnées?",
+ };
+ var overtimeMessages = {
+ ar: "هل ترغب حقًا في الموافقة على الساعات الإضافية لجميع الحضور المحدد؟",
+ de: "Möchten Sie wirklich die Überstunden für alle ausgewählten Anwesenheiten genehmigen?",
+ es: "¿Realmente quieres aprobar las horas extras para todas las asistencias seleccionadas?",
+ en: "Do you really want to approve OT for all the selected attendances?",
+ fr: "Voulez-vous vraiment approuver les heures supplémentaires pour toutes les présences sélectionnées?",
+ };
+ var hourdeleteMessages = {
+ ar: "هل ترغب حقًا في حذف جميع الحضور المحددة؟",
+ de: "Möchten Sie wirklich alle ausgewählten Anwesenheiten löschen?",
+ es: "¿Realmente quieres eliminar todas las asistencias seleccionadas?",
+ en: "Do you really want to delete all the selected attendances?",
+ fr: "Voulez-vous vraiment supprimer toutes les présences sélectionnées?",
+ };
+ var lateDeleteMessages = {
+ ar: "هل ترغب حقًا في حذف جميع الحضور المحددة؟",
+ de: "Möchten Sie wirklich alle ausgewählten Anwesenheiten löschen?",
+ es: "¿Realmente quieres eliminar todas las asistencias seleccionadas?",
+ en: "Do you really want to delete all the selected records?",
+ fr: "Voulez-vous vraiment supprimer toutes les présences sélectionnées?",
+ };
+ var noRowValidateMessages = {
+ ar: "لم يتم تحديد أي صفوف من فحص الحضور.",
+ de: "Im Feld „Anwesenheit validieren“ sind keine Zeilen ausgewählt.",
+ es: "No se selecciona ninguna fila de Validar asistencia.",
+ en: "No rows are selected from Validate Attendances.",
+ fr: "Aucune ligne n'est sélectionnée dans Valider la présence.",
+ };
+ var norowotMessages = {
+ ar: "لم يتم تحديد أي صفوف من حضور العمل الإضافي.",
+ de: "In der OT-Anwesenheit sind keine Zeilen ausgewählt.",
+ es: "No se seleccionan filas de Asistencias de OT.",
+ en: "No rows are selected from OT Attendances.",
+ fr: "Aucune ligne n'est sélectionnée dans les présences OT.",
+ };
+ var norowdeleteMessages = {
+ ar: "لم يتم تحديد أي صفوف لحذف الحضور.",
+ de: "Es sind keine Zeilen zum Löschen von Anwesenheiten ausgewählt.",
+ es: "No se seleccionan filas para eliminar asistencias.",
+ en: "No rows are selected for deleting attendances.",
+ fr: "Aucune ligne n'est sélectionnée pour la suppression des présences.",
+ };
+ var lateNorowdeleteMessages = {
+ ar: "لم يتم تحديد أي صفوف لحذف الحضور.",
+ de: "Es sind keine Zeilen zum Löschen von Anwesenheiten ausgewählt.",
+ es: "No se seleccionan filas para eliminar asistencias.",
+ en: "No rows are selected for deleting records.",
+ fr: "Aucune ligne n'est sélectionnée pour la suppression des présences.",
+ };
+ var rowMessages = {
+ ar: " تم الاختيار",
+ de: " Ausgewählt",
+ es: " Seleccionado",
+ en: " Selected",
+ fr: " Sélectionné",
+ };
+ var excelMessages = {
+ ar: "هل ترغب في تنزيل ملف Excel؟",
+ de: "Möchten Sie die Excel-Datei herunterladen?",
+ es: "¿Desea descargar el archivo de Excel?",
+ en: "Do you want to download the excel file?",
+ fr: "Voulez-vous télécharger le fichier Excel?",
+ };
+ var requestAttendanceApproveMessages = {
+ ar: "هل ترغب حقًا في الموافقة على جميع طلبات الحضور المحددة؟",
+ de: "Möchten Sie wirklich alle ausgewählten Anwesenheitsanfragen genehmigen?",
+ es: "¿Realmente quieres aprobar todas las solicitudes de asistencia seleccionadas?",
+ en: "Do you really want to approve all the selected attendance requests?",
+ fr: "Voulez-vous vraiment approuver toutes les demandes de présence sélectionnées?",
+ };
+
+ var reqAttendancRejectMessages = {
+ ar: "هل ترغب حقًا في رفض جميع طلبات الحضور المحددة؟",
+ de: "Möchten Sie wirklich alle ausgewählten Anwesenheitsanfragen ablehnen?",
+ es: "¿Realmente quieres rechazar todas las solicitudes de asistencia seleccionadas?",
+ en: "Do you really want to reject all the selected attendance requests?",
+ fr: "Voulez-vous vraiment rejeter toutes les demandes de présence sélectionnées?",
+ };
+
+ tickCheckboxes();
+ function makeListUnique(list) {
+ return Array.from(new Set(list));
+ }
+
+ tickactivityCheckboxes();
+ function makeactivityListUnique(list) {
+ return Array.from(new Set(list));
+ }
+
+ ticklatecomeCheckboxes();
+ function makelatecomeListUnique(list) {
+ return Array.from(new Set(list));
+ }
+
+ function getCurrentLanguageCode(callback) {
+ var languageCode = $("#main-section-data").attr("data-lang");
+ var allowedLanguageCodes = ["ar", "de", "es", "en", "fr"];
+ if (allowedLanguageCodes.includes(languageCode)) {
+ callback(languageCode);
+ } else {
+ $.ajax({
+ type: "GET",
+ url: "/employee/get-language-code/",
+ success: function (response) {
+ var ajaxLanguageCode = response.language_code;
+ $("#main-section-data").attr("data-lang", ajaxLanguageCode);
+ callback(
+ allowedLanguageCodes.includes(ajaxLanguageCode)
+ ? ajaxLanguageCode
+ : "en"
+ );
+ },
+ error: function () {
+ callback("en");
+ },
+ });
+ }
+ }
+
+
+ function hourAccountbulkDelete() {
+
+ var languageCode = null;
+ getCurrentLanguageCode(function (code) {
+ languageCode = code;
+ var confirmMessage = hourdeleteMessages[languageCode];
+ var textMessage = norowdeleteMessages[languageCode];
+ ids = [];
+ ids.push($("#selectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#selectedInstances").attr("data-ids"));
+ if (ids.length === 0) {
+ Swal.fire({
+ text: textMessage,
+ icon: "warning",
+ confirmButtonText: "Close",
+ });
+ } else {
+ Swal.fire({
+ text: confirmMessage,
+ icon: "error",
+ showCancelButton: true,
+ confirmButtonColor: "#008000",
+ cancelButtonColor: "#d33",
+ confirmButtonText: "Confirm",
+ }).then(function (result) {
+ if (result.isConfirmed) {
+ ids = [];
+ ids.push($("#selectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#selectedInstances").attr("data-ids"));
+ $.ajax({
+ type: "POST",
+ url: "/attendance/attendance-account-bulk-delete",
+ data: {
+ csrfmiddlewaretoken: getCookie("csrftoken"),
+ ids: JSON.stringify(ids),
+ },
+ success: function (response, textStatus, jqXHR) {
+ if (jqXHR.status === 200) {
+ location.reload();
+ }
+ },
+ });
+ }
+ });
+ }
+ });
+ };
+
+
+ function lateComeBulkDelete() {
+
+ var languageCode = null;
+ getCurrentLanguageCode(function (code) {
+ languageCode = code;
+ var confirmMessage = lateDeleteMessages[languageCode];
+ var textMessage = lateNorowdeleteMessages[languageCode];
+ ids = [];
+ ids.push($("#selectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#selectedInstances").attr("data-ids"));
+ if (ids.length === 0) {
+ Swal.fire({
+ text: textMessage,
+ icon: "warning",
+ confirmButtonText: "Close",
+ });
+ } else {
+ Swal.fire({
+ text: confirmMessage,
+ icon: "error",
+ showCancelButton: true,
+ confirmButtonColor: "#008000",
+ cancelButtonColor: "#d33",
+ confirmButtonText: "Confirm",
+ }).then(function (result) {
+ if (result.isConfirmed) {
+ ids = [];
+ ids.push($("#selectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#selectedInstances").attr("data-ids"));
+ $.ajax({
+ type: "POST",
+ url: "/attendance/late-come-early-out-bulk-delete",
+ data: {
+ csrfmiddlewaretoken: getCookie("csrftoken"),
+ ids: JSON.stringify(ids),
+ },
+ success: function (response, textStatus, jqXHR) {
+ if (jqXHR.status === 200) {
+ location.reload();
+ }
+ },
+ });
+ }
+ });
+ }
+ });
+ };
+
+
+ function reqAttendanceBulkApprove() {
+ var languageCode = null;
+ getCurrentLanguageCode(function (code) {
+ languageCode = code;
+ var confirmMessage = requestAttendanceApproveMessages[languageCode];
+ var textMessage = lateNorowdeleteMessages[languageCode];
+ ids = [];
+ ids.push($("#selectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#selectedInstances").attr("data-ids"));
+ if (ids.length === 0) {
+ Swal.fire({
+ text: textMessage,
+ icon: "warning",
+ confirmButtonText: "Close",
+ });
+ } else {
+ Swal.fire({
+ text: confirmMessage,
+ icon: "info",
+ showCancelButton: true,
+ confirmButtonColor: "#008000",
+ cancelButtonColor: "#d33",
+ confirmButtonText: "Confirm",
+ }).then(function (result) {
+ if (result.isConfirmed) {
+ ids = JSON.parse($("#selectedInstances").attr("data-ids") || "[]");
+ $.ajax({
+ type: "POST",
+ url: "/attendance/bulk-approve-attendance-request",
+ data: {
+ csrfmiddlewaretoken: getCookie("csrftoken"),
+ ids: JSON.stringify(ids),
+ },
+ success: function (response, textStatus, jqXHR) {
+ if (jqXHR.status === 200) {
+ location.reload();
+ }
+ },
+ });
+ }
+ });
+ }
+ });
+ };
+
+
+ function reqAttendanceBulkReject() {
+ var languageCode = null;
+ getCurrentLanguageCode(function (code) {
+ languageCode = code;
+ var confirmMessage = reqAttendancRejectMessages[languageCode];
+ var textMessage = noRowValidateMessages[languageCode];
+ ids = [];
+ ids.push($("#selectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#selectedInstances").attr("data-ids"));
+ if (ids.length === 0) {
+ Swal.fire({
+ text: textMessage,
+ icon: "warning",
+ confirmButtonText: "Close",
+ });
+ } else {
+ Swal.fire({
+ text: confirmMessage,
+ icon: "info",
+ showCancelButton: true,
+ confirmButtonColor: "#008000",
+ cancelButtonColor: "#d33",
+ confirmButtonText: "Confirm",
+ }).then(function (result) {
+ if (result.isConfirmed) {
+ ids = JSON.parse($("#selectedInstances").attr("data-ids") || "[]");
+ $.ajax({
+ type: "POST",
+ url: "/attendance/bulk-reject-attendance-request",
+ data: {
+ csrfmiddlewaretoken: getCookie("csrftoken"),
+ ids: JSON.stringify(ids),
+ },
+ success: function (response, textStatus, jqXHR) {
+ if (jqXHR.status === 200) {
+ location.reload();
+ }
+ },
+ });
+ }
+ });
+ }
+ });
+ };
+
+
+
\ No newline at end of file
diff --git a/attendance/static/cbv/attendance_activity.js b/attendance/static/cbv/attendance_activity.js
new file mode 100644
index 000000000..6728f1270
--- /dev/null
+++ b/attendance/static/cbv/attendance_activity.js
@@ -0,0 +1,438 @@
+var downloadMessages = {
+ ar: "هل ترغب في تنزيل القالب؟",
+ de: "Möchten Sie die Vorlage herunterladen?",
+ es: "¿Quieres descargar la plantilla?",
+ en: "Do you want to download the template?",
+ fr: "Voulez-vous télécharger le modèle ?",
+ };
+ var validateMessages = {
+ ar: "هل ترغب حقًا في التحقق من كل الحضور المحدد؟",
+ de: "Möchten Sie wirklich alle ausgewählten Anwesenheiten überprüfen?",
+ es: "¿Realmente quieres validar todas las asistencias seleccionadas?",
+ en: "Do you really want to validate all the selected attendances?",
+ fr: "Voulez-vous vraiment valider toutes les présences sélectionnées?",
+ };
+ var overtimeMessages = {
+ ar: "هل ترغب حقًا في الموافقة على الساعات الإضافية لجميع الحضور المحدد؟",
+ de: "Möchten Sie wirklich die Überstunden für alle ausgewählten Anwesenheiten genehmigen?",
+ es: "¿Realmente quieres aprobar las horas extras para todas las asistencias seleccionadas?",
+ en: "Do you really want to approve OT for all the selected attendances?",
+ fr: "Voulez-vous vraiment approuver les heures supplémentaires pour toutes les présences sélectionnées?",
+ };
+ var attendancedeleteMessages = {
+ ar: "هل ترغب حقًا في حذف جميع الحضور المحددة؟",
+ de: "Möchten Sie wirklich alle ausgewählten Anwesenheiten löschen?",
+ es: "¿Realmente quieres eliminar todas las asistencias seleccionadas?",
+ en: "Do you really want to delete all the selected attendances?",
+ fr: "Voulez-vous vraiment supprimer toutes les présences sélectionnées?",
+ };
+ var noRowValidateMessages = {
+ ar: "لم يتم تحديد أي صفوف من فحص الحضور.",
+ de: "Im Feld „Anwesenheit validieren“ sind keine Zeilen ausgewählt.",
+ es: "No se selecciona ninguna fila de Validar asistencia.",
+ en: "No rows are selected from Validate Attendances.",
+ fr: "Aucune ligne n'est sélectionnée dans Valider la présence.",
+ };
+ var norowotMessages = {
+ ar: "لم يتم تحديد أي صفوف من حضور العمل الإضافي.",
+ de: "In der OT-Anwesenheit sind keine Zeilen ausgewählt.",
+ es: "No se seleccionan filas de Asistencias de OT.",
+ en: "No rows are selected from OT Attendances.",
+ fr: "Aucune ligne n'est sélectionnée dans les présences OT.",
+ };
+ var norowdeleteMessages = {
+ ar: "لم يتم تحديد أي صفوف لحذف الحضور.",
+ de: "Es sind keine Zeilen zum Löschen von Anwesenheiten ausgewählt.",
+ es: "No se seleccionan filas para eliminar asistencias.",
+ en: "No rows are selected for deleting attendances.",
+ fr: "Aucune ligne n'est sélectionnée pour la suppression des présences.",
+ };
+ var rowMessages = {
+ ar: " تم الاختيار",
+ de: " Ausgewählt",
+ es: " Seleccionado",
+ en: " Selected",
+ fr: " Sélectionné",
+ };
+ var excelMessages = {
+ ar: "هل ترغب في تنزيل ملف Excel؟",
+ de: "Möchten Sie die Excel-Datei herunterladen?",
+ es: "¿Desea descargar el archivo de Excel?",
+ en: "Do you want to download the excel file?",
+ fr: "Voulez-vous télécharger le fichier Excel?",
+ };
+ var reqAttendanceApproveMessages = {
+ ar: "هل ترغب حقًا في الموافقة على جميع طلبات الحضور المحددة؟",
+ de: "Möchten Sie wirklich alle ausgewählten Anwesenheitsanfragen genehmigen?",
+ es: "¿Realmente quieres aprobar todas las solicitudes de asistencia seleccionadas?",
+ en: "Do you really want to approve all the selected attendance requests?",
+ fr: "Voulez-vous vraiment approuver toutes les demandes de présence sélectionnées?",
+ };
+
+ var reqAttendanceApproveMessages = {
+ ar: "هل ترغب حقًا في رفض جميع طلبات الحضور المحددة؟",
+ de: "Möchten Sie wirklich alle ausgewählten Anwesenheitsanfragen ablehnen?",
+ es: "¿Realmente quieres rechazar todas las solicitudes de asistencia seleccionadas?",
+ en: "Do you really want to reject all the selected attendance requests?",
+ fr: "Voulez-vous vraiment rejeter toutes les demandes de présence sélectionnées?",
+ };
+
+ tickCheckboxes();
+ function makeListUnique(list) {
+ return Array.from(new Set(list));
+ }
+
+ tickactivityCheckboxes();
+ function makeactivityListUnique(list) {
+ return Array.from(new Set(list));
+ }
+
+ ticklatecomeCheckboxes();
+ function makelatecomeListUnique(list) {
+ return Array.from(new Set(list));
+ }
+
+ function getCurrentLanguageCode(callback) {
+ var languageCode = $("#main-section-data").attr("data-lang");
+ var allowedLanguageCodes = ["ar", "de", "es", "en", "fr"];
+ if (allowedLanguageCodes.includes(languageCode)) {
+ callback(languageCode);
+ } else {
+ $.ajax({
+ type: "GET",
+ url: "/employee/get-language-code/",
+ success: function (response) {
+ var ajaxLanguageCode = response.language_code;
+ $("#main-section-data").attr("data-lang", ajaxLanguageCode);
+ callback(
+ allowedLanguageCodes.includes(ajaxLanguageCode)
+ ? ajaxLanguageCode
+ : "en"
+ );
+ },
+ error: function () {
+ callback("en");
+ },
+ });
+ }
+ }
+
+
+ function getCookie(name) {
+ let cookieValue = null;
+ if (document.cookie && document.cookie !== "") {
+ const cookies = document.cookie.split(";");
+ for (let i = 0; i < cookies.length; i++) {
+ const cookie = cookies[i].trim();
+ // Does this cookie string begin with the name we want?
+ if (cookie.substring(0, name.length + 1) === name + "=") {
+ cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+ break;
+ }
+ }
+ }
+ return cookieValue;
+ }
+
+
+
+
+function deleteAttendanceNav() {
+ var languageCode = null;
+ getCurrentLanguageCode(function (code) {
+ languageCode = code;
+ var confirmMessage = attendancedeleteMessages[languageCode];
+ var textMessage = norowdeleteMessages[languageCode];
+ ids = [];
+ ids.push($("#selectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#selectedInstances").attr("data-ids"));
+ if (ids.length === 0) {
+ Swal.fire({
+ text: textMessage,
+ icon: "warning",
+ confirmButtonText: "Close",
+ });
+ } else {
+ Swal.fire({
+ text: confirmMessage,
+ icon: "error",
+ showCancelButton: true,
+ confirmButtonColor: "#008000",
+ cancelButtonColor: "#d33",
+ confirmButtonText: "Confirm",
+ }).then(function (result) {
+ if (result.isConfirmed) {
+ ids = [];
+ ids.push($("#selectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#selectedInstances").attr("data-ids"));
+ $.ajax({
+ type: "POST",
+ url: "/attendance/attendance-activity-bulk-delete",
+ data: {
+ csrfmiddlewaretoken: getCookie("csrftoken"),
+ ids: JSON.stringify(ids),
+ },
+ success: function (response, textStatus, jqXHR) {
+ if (jqXHR.status === 200) {
+ location.reload();
+ }
+ },
+ });
+ }
+ });
+ }
+ });
+ }
+
+
+ function importAttendanceNav() {
+ var languageCode = null;
+ getCurrentLanguageCode(function (code) {
+ languageCode = code;
+ var confirmMessage = downloadMessages[languageCode];
+ Swal.fire({
+ text: confirmMessage,
+ icon: "question",
+ showCancelButton: true,
+ confirmButtonColor: "#008000",
+ cancelButtonColor: "#d33",
+ confirmButtonText: "Confirm",
+ }).then(function (result) {
+ if (result.isConfirmed) {
+ $.ajax({
+ type: "GET",
+ url: "/attendance/attendance-excel",
+ dataType: "binary",
+ xhrFields: {
+ responseType: "blob",
+ },
+ success: function (response) {
+ 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 = "attendance_excel.xlsx";
+ document.body.appendChild(link);
+ link.click();
+ },
+ error: function (xhr, textStatus, errorThrown) {
+ console.error("Error downloading file:", errorThrown);
+ },
+ });
+ }
+ });
+ });
+ }
+
+
+
+ function importAttendanceActivity() {
+ var languageCode = null;
+ getCurrentLanguageCode(function (code) {
+ languageCode = code;
+ var confirmMessage = downloadMessages[languageCode];
+ Swal.fire({
+ text: confirmMessage,
+ icon: "question",
+ showCancelButton: true,
+ confirmButtonColor: "#008000",
+ cancelButtonColor: "#d33",
+ confirmButtonText: "Confirm",
+ }).then(function (result) {
+ if (result.isConfirmed) {
+ $.ajax({
+ type: "GET",
+ url: "/attendance/attendance-activity-import-excel",
+ dataType: "binary",
+ xhrFields: {
+ responseType: "blob",
+ },
+ success: function (response) {
+ 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 = "activity_excel.xlsx";
+ document.body.appendChild(link);
+ link.click();
+ },
+ error: function (xhr, textStatus, errorThrown) {
+ console.error("Error downloading file:", errorThrown);
+ },
+ });
+ }
+ });
+ });
+ }
+
+
+ function bulkDeleteAttendanceNav() {
+ var languageCode = null;
+ getCurrentLanguageCode(function (code) {
+ languageCode = code;
+ var confirmMessage = attendancedeleteMessages[languageCode];
+ var textMessage = norowdeleteMessages[languageCode];
+ ids = [];
+ ids.push($("#selectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#selectedInstances").attr("data-ids"));
+ if (ids.length === 0) {
+ Swal.fire({
+ text: textMessage,
+ icon: "warning",
+ confirmButtonText: "Close",
+ });
+ } else {
+ Swal.fire({
+ text: confirmMessage,
+ icon: "error",
+ showCancelButton: true,
+ confirmButtonColor: "#008000",
+ cancelButtonColor: "#d33",
+ confirmButtonText: "Confirm",
+ }).then(function (result) {
+ if (result.isConfirmed) {
+ ids = [];
+ ids.push($("#selectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#selectedInstances").attr("data-ids"));
+ $.ajax({
+ type: "POST",
+ url: "/attendance/attendance-bulk-delete",
+ data: {
+ csrfmiddlewaretoken: getCookie("csrftoken"),
+ ids: JSON.stringify(ids),
+ },
+ success: function (response, textStatus, jqXHR) {
+ if (jqXHR.status === 200) {
+ location.reload();
+ }
+ },
+ });
+ }
+ });
+ }
+ });
+ }
+
+
+
+
+ function showApproveAlert(dataReqValue) {
+ Swal.fire({
+ title: 'Pending Attendance Update Request!',
+ text: 'An attendance request exists for updating this attendance prior to validation.',
+ icon: 'warning',
+ confirmButtonText: 'View Request',
+ showCancelButton: true,
+ cancelButtonText: 'Close',
+ preConfirm: () => {
+ // Redirect to the page based on dataReqValue
+ localStorage.setItem("attendanceRequestActiveTab","#tab_1")
+ window.location.href = dataReqValue;
+
+ },
+ });
+ }
+
+
+ function bulkValidateTabAttendance(dataReqValue) {
+ var languageCode = null;
+ getCurrentLanguageCode(function (code) {
+ languageCode = code;
+ var confirmMessage = validateMessages[languageCode];
+ var textMessage = noRowValidateMessages[languageCode];
+ ids = [];
+ ids.push($("#validateselectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#validateselectedInstances").attr("data-ids"));
+ if (ids.length === 0) {
+ Swal.fire({
+ text: textMessage,
+ icon: "warning",
+ confirmButtonText: "Close",
+ });
+ } else {
+ Swal.fire({
+ text: confirmMessage,
+ icon: "info",
+ showCancelButton: true,
+ confirmButtonColor: "#008000",
+ cancelButtonColor: "#d33",
+ confirmButtonText: "Confirm",
+ }).then(function (result) {
+ if (result.isConfirmed) {
+ ids = [];
+ ids.push($("#validateselectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#validateselectedInstances").attr("data-ids"));
+ $.ajax({
+ type: "POST",
+ url: "/attendance/validate-bulk-attendance",
+ data: {
+ csrfmiddlewaretoken: getCookie("csrftoken"),
+ ids: JSON.stringify(ids),
+ },
+ success: function (response, textStatus, jqXHR) {
+ if (jqXHR.status === 200) {
+ location.reload();
+ } else {
+ }
+ },
+ });
+ }
+ });
+ }
+ });
+ }
+
+ function otBulkValidateTabAttendance(dataReqValue) {
+ var languageCode = null;
+ getCurrentLanguageCode(function (code) {
+ languageCode = code;
+ var confirmMessage = overtimeMessages[languageCode];
+ var textMessage = norowotMessages[languageCode];
+ ids = [];
+ ids.push($("#overtimeselectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#overtimeselectedInstances").attr("data-ids"));
+ if (ids.length === 0) {
+ Swal.fire({
+ text: textMessage,
+ icon: "warning",
+ confirmButtonText: "Close",
+ });
+ } else {
+ Swal.fire({
+ text: confirmMessage,
+ icon: "success",
+ showCancelButton: true,
+ confirmButtonColor: "#008000",
+ cancelButtonColor: "#d33",
+ confirmButtonText: "Confirm",
+ }).then(function (result) {
+ if (result.isConfirmed) {
+ ids = [];
+ ids.push($("#overtimeselectedInstances").attr("data-ids"));
+ ids = JSON.parse($("#overtimeselectedInstances").attr("data-ids"));
+
+ $.ajax({
+ type: "POST",
+ url: "/attendance/approve-bulk-overtime",
+ data: {
+ csrfmiddlewaretoken: getCookie("csrftoken"),
+ ids: JSON.stringify(ids),
+ },
+ success: function (response, textStatus, jqXHR) {
+ if (jqXHR.status === 200) {
+ location.reload();
+ } else {
+ }
+ },
+ });
+ }
+ });
+ }
+ });
+ }
+
+
\ No newline at end of file
diff --git a/attendance/templates/attendance/attendance_activity/export_filter.html b/attendance/templates/attendance/attendance_activity/export_filter.html
index 5c43be942..9ca69bade 100644
--- a/attendance/templates/attendance/attendance_activity/export_filter.html
+++ b/attendance/templates/attendance/attendance_activity/export_filter.html
@@ -1,17 +1,5 @@
{% load static %} {% load i18n %}
-
-
-
-
-
diff --git a/attendance/templates/attendance/break_point/condition.html b/attendance/templates/attendance/break_point/condition.html
index 910d2bccc..784acc818 100644
--- a/attendance/templates/attendance/break_point/condition.html
+++ b/attendance/templates/attendance/break_point/condition.html
@@ -1,4 +1,8 @@
-{% extends 'settings.html' %} {% load i18n %} {% block settings %}{% load static %}
+{% extends 'settings.html' %}
+{% load i18n %}
+{% block settings %}
+{% load static %}
+{% include "generic/components.html" %}
- {% if condition %}
-
+
+ {% comment %} {% if condition %} {% endcomment %}
+ {% comment %}
@@ -35,12 +40,26 @@
{% trans "Actions" %}
{% endif %}
-
-
- {% if condition != None %}
-
+
{% endcomment %}
+ {% comment %}
{% endcomment %}
+ {% comment %} {% if condition != None %} {% endcomment %}
+
+
+ {% comment %}
{{ condition.validation_at_work }}
{{ condition.minimum_overtime_to_approve }}
+
{{ condition.overtime_cutoff }}
{% endcomment %}
+ {% comment %} {% if perms.attendance.change_attendancevalidationcondition %}
{{ condition.overtime_cutoff }}
{{ condition.auto_approve_ot|yesno:"Yes,No" }}
{% if perms.attendance.change_attendancevalidationcondition %}
@@ -49,17 +68,18 @@
hx-target="#objectUpdateModalTarget" type="button" class="oh-btn oh-btn--info"
data-toggle="oh-modal-toggle" data-target="#objectUpdateModal">{% trans 'Edit' %}
- {% endif %}
-
- {% endif %}
-
+ {% endif %} {% endcomment %}
+ {% comment %}
{% endcomment %}
+ {% comment %} {% endif %} {% endcomment %}
+ {% comment %}
{% endcomment %}
+ {% comment %}
-
- {% else %}
+ {% endcomment %}
+ {% comment %} {% else %}
{% trans "There is no attendance conditions at this moment." %}
-
- {% endif %}
+ {% endcomment %}
+ {% comment %} {% endif %} {% endcomment %}
{% endblock %}
diff --git a/attendance/templates/attendance/dashboard/dashboard.html b/attendance/templates/attendance/dashboard/dashboard.html
index f92d97e34..ba0b06f4c 100644
--- a/attendance/templates/attendance/dashboard/dashboard.html
+++ b/attendance/templates/attendance/dashboard/dashboard.html
@@ -1,5 +1,6 @@
{% extends 'index.html' %} {% load i18n %} {% block content %} {% load static %}
{% load attendancefilters %}
+{% include 'generic/components.html' %}
+
+
+
+
+
+
+
@@ -104,9 +123,11 @@
{% trans 'Offline Employees' %}
-
diff --git a/attendance/templates/attendance/grace_time/grace_time_table.html b/attendance/templates/attendance/grace_time/grace_time_table.html
index afa23d202..f3ee25c0e 100644
--- a/attendance/templates/attendance/grace_time/grace_time_table.html
+++ b/attendance/templates/attendance/grace_time/grace_time_table.html
@@ -1,4 +1,5 @@
{% load static %}{% load i18n %}
+{% include "generic/components.html" %}
{% if messages %}
@@ -12,7 +13,11 @@
{% endif %}