1
asset/__init__.py
Normal file
1
asset/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from asset import scheduler
|
||||||
34
asset/admin.py
Normal file
34
asset/admin.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
"""
|
||||||
|
Module: admin.py
|
||||||
|
Description: This module is responsible for registering models
|
||||||
|
to be managed through the Django admin interface.
|
||||||
|
Models Registered:
|
||||||
|
- Asset: Represents a physical asset with relevant details.
|
||||||
|
- AssetCategory: Categorizes assets for better organization.
|
||||||
|
- AssetRequest: Manages requests for acquiring assets.
|
||||||
|
- AssetAssignment: Tracks the assets assigned to employees.
|
||||||
|
- AssetLot: Represents a collection of assets under a lot number.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
Asset,
|
||||||
|
AssetAssignment,
|
||||||
|
AssetCategory,
|
||||||
|
AssetDocuments,
|
||||||
|
AssetLot,
|
||||||
|
AssetReport,
|
||||||
|
AssetRequest,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Asset)
|
||||||
|
admin.site.register(AssetRequest)
|
||||||
|
admin.site.register(AssetCategory)
|
||||||
|
admin.site.register(AssetAssignment)
|
||||||
|
admin.site.register(AssetLot)
|
||||||
|
admin.site.register(AssetReport)
|
||||||
|
admin.site.register(AssetDocuments)
|
||||||
33
asset/apps.py
Normal file
33
asset/apps.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"""
|
||||||
|
Module: apps.py
|
||||||
|
Description: Configuration for the 'asset' app.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AssetConfig(AppConfig):
|
||||||
|
"""
|
||||||
|
Class: AssetConfig
|
||||||
|
Description: Configuration class for the 'asset' app.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
default_auto_field (str): Default auto-generated field type for primary keys.
|
||||||
|
name (str): Name of the app.
|
||||||
|
"""
|
||||||
|
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "asset"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
from horilla.horilla_settings import APP_URLS, APPS
|
||||||
|
from horilla.urls import urlpatterns
|
||||||
|
|
||||||
|
APPS.append("asset")
|
||||||
|
urlpatterns.append(
|
||||||
|
path("asset/", include("asset.urls")),
|
||||||
|
)
|
||||||
|
APP_URLS.append("asset.urls")
|
||||||
|
super().ready()
|
||||||
375
asset/filters.py
Normal file
375
asset/filters.py
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
"""
|
||||||
|
Module containing custom filter classes for various models.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import django_filters
|
||||||
|
from django import forms
|
||||||
|
from django.db.models import Q
|
||||||
|
from django_filters import FilterSet
|
||||||
|
|
||||||
|
from base.methods import reload_queryset
|
||||||
|
|
||||||
|
from .models import Asset, AssetAssignment, AssetCategory, AssetRequest
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFilterSet(FilterSet):
|
||||||
|
"""
|
||||||
|
Custom FilterSet class that applies specific CSS classes to filter
|
||||||
|
widgets.
|
||||||
|
|
||||||
|
The class applies CSS classes to different types of filter widgets,
|
||||||
|
such as NumberInput, EmailInput, TextInput, Select, Textarea,
|
||||||
|
CheckboxInput, CheckboxSelectMultiple, and ModelChoiceField. The
|
||||||
|
CSS classes are applied to enhance the styling and behavior of the
|
||||||
|
filter widgets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
reload_queryset(self.form.fields)
|
||||||
|
for field_name, field in self.form.fields.items():
|
||||||
|
filter_widget = self.filters[field_name]
|
||||||
|
widget = filter_widget.field.widget
|
||||||
|
if isinstance(
|
||||||
|
widget, (forms.NumberInput, forms.EmailInput, forms.TextInput)
|
||||||
|
):
|
||||||
|
field.widget.attrs.update({"class": "oh-input w-100"})
|
||||||
|
elif isinstance(widget, (forms.Select,)):
|
||||||
|
field.widget.attrs.update(
|
||||||
|
{
|
||||||
|
"class": "oh-select oh-select-2",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
elif isinstance(widget, (forms.Textarea)):
|
||||||
|
field.widget.attrs.update({"class": "oh-input w-100"})
|
||||||
|
elif isinstance(
|
||||||
|
widget,
|
||||||
|
(
|
||||||
|
forms.CheckboxInput,
|
||||||
|
forms.CheckboxSelectMultiple,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
filter_widget.field.widget.attrs.update(
|
||||||
|
{"class": "oh-switch__checkbox"}
|
||||||
|
)
|
||||||
|
elif isinstance(widget, (forms.ModelChoiceField)):
|
||||||
|
field.widget.attrs.update(
|
||||||
|
{
|
||||||
|
"class": "oh-select oh-select-2 ",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
elif isinstance(widget, (forms.DateField)):
|
||||||
|
field.widget.attrs.update({"type": "date", "class": "oh-input w-100"})
|
||||||
|
if isinstance(field, django_filters.CharFilter):
|
||||||
|
field.lookup_expr = "icontains"
|
||||||
|
|
||||||
|
|
||||||
|
class AssetExportFilter(CustomFilterSet):
|
||||||
|
"""
|
||||||
|
Custom filter class for exporting filtered Asset data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
A nested class that specifies the configuration for the filter.
|
||||||
|
model(class): The Asset model is used to filter.
|
||||||
|
fields (str): A special value "__all__" to include all fields
|
||||||
|
of the model in the filter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = Asset
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.form.fields["asset_purchase_date"].widget.attrs.update({"type": "date"})
|
||||||
|
|
||||||
|
|
||||||
|
class AssetFilter(CustomFilterSet):
|
||||||
|
"""
|
||||||
|
Custom filter set for Asset instances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
search = django_filters.CharFilter(method="search_method")
|
||||||
|
category = django_filters.CharFilter(field_name="asset_category_id")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
A nested class that specifies the configuration for the filter.
|
||||||
|
model(class): The Asset model is used to filter.
|
||||||
|
fields (str): A special value "__all__" to include all fields
|
||||||
|
of the model in the filter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = Asset
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
for visible in self.form.visible_fields():
|
||||||
|
visible.field.widget.attrs["id"] = str(uuid.uuid4())
|
||||||
|
|
||||||
|
def search_method(self, queryset, _, value):
|
||||||
|
"""
|
||||||
|
Search method
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
queryset.filter(asset_name__icontains=value)
|
||||||
|
| queryset.filter(asset_category_id__asset_category_name__icontains=value)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
|
||||||
|
class CustomAssetFilter(CustomFilterSet):
|
||||||
|
"""
|
||||||
|
Custom filter set for asset assigned to employees instances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
asset_id__asset_name = django_filters.CharFilter(lookup_expr="icontains")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Specifies the model and fields to be used for filtering AssetAssignment instances.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
model (class): The model class AssetAssignment to be filtered.
|
||||||
|
fields (list): The fields to include in the filter, referring to
|
||||||
|
related AssetAssignment fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = AssetAssignment
|
||||||
|
fields = [
|
||||||
|
"asset_id__asset_name",
|
||||||
|
"asset_id__asset_status",
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
for visible in self.form.visible_fields():
|
||||||
|
visible.field.widget.attrs["id"] = str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
||||||
|
class AssetRequestFilter(CustomFilterSet):
|
||||||
|
"""
|
||||||
|
Custom filter set for AssetRequest instances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
search = django_filters.CharFilter(method="search_method")
|
||||||
|
|
||||||
|
def search_method(self, queryset, _, value: str):
|
||||||
|
"""
|
||||||
|
This method is used to search employees
|
||||||
|
"""
|
||||||
|
values = value.split(" ")
|
||||||
|
empty = queryset.model.objects.none()
|
||||||
|
for split in values:
|
||||||
|
empty = empty | (
|
||||||
|
queryset.filter(
|
||||||
|
requested_employee_id__employee_first_name__icontains=split
|
||||||
|
)
|
||||||
|
| queryset.filter(
|
||||||
|
requested_employee_id__employee_last_name__icontains=split
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return empty.distinct()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Specifies the model and fields to be used for filtering AssetRequest instances.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
model (class): The model class AssetRequest to be filtered.
|
||||||
|
fields (str): A special value "__all__" to include all fields of the model in the filter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = AssetRequest
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
for visible in self.form.visible_fields():
|
||||||
|
visible.field.widget.attrs["id"] = str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
||||||
|
class AssetAllocationFilter(CustomFilterSet):
|
||||||
|
"""
|
||||||
|
Custom filter set for AssetAllocation instances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
search = django_filters.CharFilter(method="search_method")
|
||||||
|
|
||||||
|
def search_method(self, queryset, _, value: str):
|
||||||
|
"""
|
||||||
|
This method is used to search employees
|
||||||
|
"""
|
||||||
|
values = value.split(" ")
|
||||||
|
empty = queryset.model.objects.none()
|
||||||
|
for split in values:
|
||||||
|
empty = empty | (
|
||||||
|
queryset.filter(
|
||||||
|
assigned_to_employee_id__employee_first_name__icontains=split
|
||||||
|
)
|
||||||
|
| queryset.filter(
|
||||||
|
assigned_to_employee_id__employee_last_name__icontains=split
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return empty.distinct()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Specifies the model and fields to be used for filtering AssetAllocation instances.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
model (class): The model class AssetAssignment to be filtered.
|
||||||
|
fields (str): A special value "__all__" to include all fields
|
||||||
|
of the model in the filter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = AssetAssignment
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
for visible in self.form.visible_fields():
|
||||||
|
visible.field.widget.attrs["id"] = str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
||||||
|
class AssetCategoryFilter(CustomFilterSet):
|
||||||
|
"""
|
||||||
|
Custom filter set for AssetCategory instances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
search = django_filters.CharFilter(method="search_method")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AssetCategory
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
for visible in self.form.visible_fields():
|
||||||
|
visible.field.widget.attrs["id"] = str(uuid.uuid4())
|
||||||
|
|
||||||
|
def search_method(self, queryset, name, value):
|
||||||
|
"""
|
||||||
|
Search method to filter by asset category name or related asset name.
|
||||||
|
"""
|
||||||
|
if not value:
|
||||||
|
return queryset # Return unfiltered queryset if no search term is provided
|
||||||
|
|
||||||
|
return queryset.filter(
|
||||||
|
Q(asset_category_name__icontains=value)
|
||||||
|
| Q(asset__asset_name__icontains=value)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
"""
|
||||||
|
Filters queryset and applies AssetFilter if necessary.
|
||||||
|
"""
|
||||||
|
# Get the base filtered queryset
|
||||||
|
queryset = super().filter_queryset(queryset)
|
||||||
|
|
||||||
|
# Filter by assets if asset data is present in the GET request
|
||||||
|
if self.data and "asset__pk" in self.data:
|
||||||
|
assets = AssetFilter(data=self.data).qs
|
||||||
|
queryset = queryset.filter(
|
||||||
|
asset__pk__in=assets.values_list("pk", flat=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
return queryset.distinct()
|
||||||
|
|
||||||
|
|
||||||
|
class AssetRequestReGroup:
|
||||||
|
"""
|
||||||
|
Class to keep the field name for group by option
|
||||||
|
"""
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
("", "Select"),
|
||||||
|
("requested_employee_id", "Employee"),
|
||||||
|
("asset_category_id", "Asset Category"),
|
||||||
|
("asset_request_date", "Request Date"),
|
||||||
|
("asset_request_status", "Status"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class AssetAllocationReGroup:
|
||||||
|
"""
|
||||||
|
Class to keep the field name for group by option
|
||||||
|
"""
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
("", "Select"),
|
||||||
|
("assigned_to_employee_id", "Employee"),
|
||||||
|
("assigned_date", "Assigned Date"),
|
||||||
|
("return_date", "Return Date"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class AssetHistoryFilter(CustomFilterSet):
|
||||||
|
"""
|
||||||
|
Custom filter set for AssetAssignment instances for filtering in asset history view.
|
||||||
|
"""
|
||||||
|
|
||||||
|
search = django_filters.CharFilter(
|
||||||
|
field_name="asset_id__asset_name", lookup_expr="icontains"
|
||||||
|
)
|
||||||
|
returned_assets = django_filters.CharFilter(
|
||||||
|
field_name="return_status", method="exclude_none"
|
||||||
|
)
|
||||||
|
return_date_gte = django_filters.DateFilter(
|
||||||
|
field_name="return_date",
|
||||||
|
lookup_expr="gte",
|
||||||
|
widget=forms.DateInput(attrs={"type": "date"}),
|
||||||
|
)
|
||||||
|
return_date_lte = django_filters.DateFilter(
|
||||||
|
field_name="return_date",
|
||||||
|
lookup_expr="lte",
|
||||||
|
widget=forms.DateInput(attrs={"type": "date"}),
|
||||||
|
)
|
||||||
|
assigned_date_gte = django_filters.DateFilter(
|
||||||
|
field_name="assigned_date",
|
||||||
|
lookup_expr="gte",
|
||||||
|
widget=forms.DateInput(attrs={"type": "date"}),
|
||||||
|
)
|
||||||
|
assigned_date_lte = django_filters.DateFilter(
|
||||||
|
field_name="assigned_date",
|
||||||
|
lookup_expr="lte",
|
||||||
|
widget=forms.DateInput(attrs={"type": "date"}),
|
||||||
|
)
|
||||||
|
|
||||||
|
def exclude_none(self, queryset, name, value):
|
||||||
|
"""
|
||||||
|
Exclude objects with a null return_status from the queryset if value is "True"
|
||||||
|
"""
|
||||||
|
if value == "True":
|
||||||
|
queryset = queryset.filter(return_status__isnull=False)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Specifies the model and fields to be used for filtering AssetAllocation instances.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
model (class): The model class AssetAssignment to be filtered.
|
||||||
|
fields (str): A special value "__all__" to include all fields
|
||||||
|
of the model in the filter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = AssetAssignment
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class AssetHistoryReGroup:
|
||||||
|
"""
|
||||||
|
Class to keep the field name for group by option
|
||||||
|
"""
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
("", "Select"),
|
||||||
|
("asset_id", "Asset"),
|
||||||
|
("assigned_to_employee_id", "Employee"),
|
||||||
|
("assigned_date", "Assigned Date"),
|
||||||
|
("return_date", "Return Date"),
|
||||||
|
]
|
||||||
384
asset/forms.py
Normal file
384
asset/forms.py
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
"""
|
||||||
|
forms.py
|
||||||
|
Asset Management Forms
|
||||||
|
|
||||||
|
This module contains Django ModelForms for handling various aspects of asset management,
|
||||||
|
including asset creation, allocation, return, category assignment, and batch handling.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from asset.models import (
|
||||||
|
Asset,
|
||||||
|
AssetAssignment,
|
||||||
|
AssetCategory,
|
||||||
|
AssetDocuments,
|
||||||
|
AssetLot,
|
||||||
|
AssetReport,
|
||||||
|
AssetRequest,
|
||||||
|
)
|
||||||
|
from base.forms import ModelForm
|
||||||
|
from base.methods import reload_queryset
|
||||||
|
from employee.forms import MultipleFileField
|
||||||
|
from employee.models import Employee
|
||||||
|
from horilla.horilla_middlewares import _thread_locals
|
||||||
|
|
||||||
|
|
||||||
|
def set_date_field_initial(instance):
|
||||||
|
"""this is used to update change the date value format"""
|
||||||
|
initial = {}
|
||||||
|
if instance.asset_purchase_date is not None:
|
||||||
|
initial["asset_purchase_date"] = instance.asset_purchase_date.strftime(
|
||||||
|
"%Y-%m-%d"
|
||||||
|
)
|
||||||
|
|
||||||
|
return initial
|
||||||
|
|
||||||
|
|
||||||
|
class AssetForm(ModelForm):
|
||||||
|
"""
|
||||||
|
A ModelForm for creating and updating asset information.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Asset
|
||||||
|
fields = "__all__"
|
||||||
|
exclude = ["is_active"]
|
||||||
|
widgets = {
|
||||||
|
"asset_lot_number_id": forms.Select(
|
||||||
|
attrs={"onchange": "batchNoChange($(this))"}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
request = getattr(_thread_locals, "request", None)
|
||||||
|
instance = kwargs.get("instance")
|
||||||
|
|
||||||
|
if instance:
|
||||||
|
kwargs.setdefault("initial", set_date_field_initial(instance))
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
uuid_map = {
|
||||||
|
field: str(uuid.uuid4())
|
||||||
|
for field in ["asset_category_id", "asset_lot_number_id", "asset_status"]
|
||||||
|
}
|
||||||
|
for field, uuid_value in uuid_map.items():
|
||||||
|
self.fields[field].widget.attrs["id"] = uuid_value
|
||||||
|
|
||||||
|
if request and request.user.has_perm("asset.add_assetlot"):
|
||||||
|
batch_no_choices = list(
|
||||||
|
self.fields["asset_lot_number_id"].queryset.values_list(
|
||||||
|
"id", "lot_number"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
batch_no_choices.insert(0, ("", _("---Choose Batch No.---")))
|
||||||
|
|
||||||
|
if not self.instance.pk:
|
||||||
|
batch_no_choices.append(("create", _("Create new batch number")))
|
||||||
|
|
||||||
|
self.fields["asset_lot_number_id"].choices = batch_no_choices
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
instance = self.instance
|
||||||
|
prev_instance = Asset.objects.filter(id=instance.pk).first()
|
||||||
|
if instance.pk:
|
||||||
|
if (
|
||||||
|
self.cleaned_data.get("asset_status", None)
|
||||||
|
and self.cleaned_data.get("asset_status", None)
|
||||||
|
!= prev_instance.asset_status
|
||||||
|
):
|
||||||
|
if instance.assetassignment_set.filter(
|
||||||
|
return_status__isnull=True
|
||||||
|
).exists():
|
||||||
|
raise ValidationError(
|
||||||
|
{"asset_status": 'Asset in use you can"t change the status'}
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
Asset.objects.filter(asset_tracking_id=self.data["asset_tracking_id"])
|
||||||
|
.exclude(id=instance.pk)
|
||||||
|
.exists()
|
||||||
|
):
|
||||||
|
raise ValidationError(
|
||||||
|
{"asset_tracking_id": "Already asset with this tracking id exists."}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
Form for uploading documents related to an asset.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
- file: A FileField with a TextInput widget for file upload, allowing multiple files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
file = forms.FileField(
|
||||||
|
widget=forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
"name": "file",
|
||||||
|
"type": "File",
|
||||||
|
"class": "form-control",
|
||||||
|
"multiple": "True",
|
||||||
|
"accept": ".jpeg, .jpg, .png, .pdf",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Metadata options for the DocumentForm.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
- model: The model associated with this form (AssetDocuments).
|
||||||
|
- fields: Fields to include in the form ('file').
|
||||||
|
- exclude: Fields to exclude from the form ('is_active').
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = AssetDocuments
|
||||||
|
fields = [
|
||||||
|
"file",
|
||||||
|
]
|
||||||
|
exclude = ["is_active"]
|
||||||
|
|
||||||
|
|
||||||
|
class AssetReportForm(ModelForm):
|
||||||
|
"""
|
||||||
|
Form for creating and updating asset reports.
|
||||||
|
|
||||||
|
Metadata:
|
||||||
|
- model: The model associated with this form (AssetReport).
|
||||||
|
- fields: Fields to include in the form ('title', 'asset_id').
|
||||||
|
- exclude: Fields to exclude from the form ('is_active').
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
- __init__: Initializes the form, disabling the 'asset_id' field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Metadata options for the AssetReportForm.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
- model: The model associated with this form (AssetReport).
|
||||||
|
- fields: Fields to include in the form ('title', 'asset_id').
|
||||||
|
- exclude: Fields to exclude from the form ('is_active').
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = AssetReport
|
||||||
|
fields = [
|
||||||
|
"title",
|
||||||
|
"asset_id",
|
||||||
|
]
|
||||||
|
exclude = ["is_active"]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize the AssetReportForm, disabling the 'asset_id' field.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- *args: Variable length argument list.
|
||||||
|
- **kwargs: Arbitrary keyword arguments.
|
||||||
|
"""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields["asset_id"].widget.attrs["disabled"] = "disabled"
|
||||||
|
|
||||||
|
|
||||||
|
class AssetCategoryForm(ModelForm):
|
||||||
|
"""
|
||||||
|
A form for creating and updating AssetCategory instances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Specifies the model and fields to be used for the AssetForm.
|
||||||
|
Attributes:
|
||||||
|
model (class): The model class AssetCategory to be used for the form.
|
||||||
|
fields (str): A special value "__all__" to include all fields
|
||||||
|
of the model in the form.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = AssetCategory
|
||||||
|
fields = "__all__"
|
||||||
|
exclude = ["is_active"]
|
||||||
|
|
||||||
|
|
||||||
|
class AssetRequestForm(ModelForm):
|
||||||
|
"""
|
||||||
|
A Django ModelForm for creating and updating AssetRequest instances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Specifies the model and fields to be used for the AssetRequestForm.
|
||||||
|
Attributes:
|
||||||
|
model (class): The model class AssetRequest to be used for the form.
|
||||||
|
fields (str): A special value "__all__" to include all fields
|
||||||
|
of the model in the form.
|
||||||
|
widgets (dict): A dictionary containing widget configurations for
|
||||||
|
specific form fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = AssetRequest
|
||||||
|
fields = "__all__"
|
||||||
|
exclude = ["is_active"]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
user = kwargs.pop("user", None)
|
||||||
|
super().__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
|
||||||
|
).first()
|
||||||
|
else:
|
||||||
|
self.fields["requested_employee_id"].queryset = Employee.objects.filter(
|
||||||
|
employee_user_id=user
|
||||||
|
)
|
||||||
|
self.fields["requested_employee_id"].initial = user.employee_get
|
||||||
|
|
||||||
|
self.fields["asset_category_id"].widget.attrs.update({"id": str(uuid.uuid4())})
|
||||||
|
|
||||||
|
|
||||||
|
class AssetAllocationForm(ModelForm):
|
||||||
|
"""
|
||||||
|
A Django ModelForm for creating and updating AssetAssignment instances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
reload_queryset(self.fields)
|
||||||
|
self.fields["asset_id"].queryset = Asset.objects.filter(
|
||||||
|
asset_status="Available"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.fields["assign_images"] = MultipleFileField(
|
||||||
|
label=_("Assign Condition Images")
|
||||||
|
)
|
||||||
|
self.fields["assign_images"].required = True
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Specifies the model and fields to be used for the AssetAllocationForm.
|
||||||
|
Attributes:
|
||||||
|
model (class): The model class AssetAssignment to be used for the form.
|
||||||
|
fields (str): A special value "__all__" to include all fields
|
||||||
|
of the model in the form.
|
||||||
|
widgets (dict): A dictionary containing widget configurations for
|
||||||
|
specific form fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = AssetAssignment
|
||||||
|
fields = "__all__"
|
||||||
|
exclude = [
|
||||||
|
"return_date",
|
||||||
|
"return_condition",
|
||||||
|
"assigned_date",
|
||||||
|
"return_images",
|
||||||
|
"is_active",
|
||||||
|
]
|
||||||
|
widgets = {
|
||||||
|
"asset_id": forms.Select(attrs={"class": "oh-select oh-select-2 "}),
|
||||||
|
"assigned_to_employee_id": forms.Select(
|
||||||
|
attrs={"class": "oh-select oh-select-2 "}
|
||||||
|
),
|
||||||
|
"assigned_by_employee_id": forms.Select(
|
||||||
|
attrs={
|
||||||
|
"class": "oh-select oh-select-2 ",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
# def clean(self):
|
||||||
|
# cleaned_data = super.clean()
|
||||||
|
|
||||||
|
|
||||||
|
class AssetReturnForm(ModelForm):
|
||||||
|
"""
|
||||||
|
A Django ModelForm for updating AssetAssignment instances during asset return.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Specifies the model and fields to be used for the AssetReturnForm.
|
||||||
|
Attributes:
|
||||||
|
model (class): The model class AssetAssignment to be used for the form.
|
||||||
|
fields (list): The fields to include in the form, referring to
|
||||||
|
related AssetAssignment fields.
|
||||||
|
widgets (dict): A dictionary containing widget configurations for
|
||||||
|
specific form fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = AssetAssignment
|
||||||
|
fields = ["return_date", "return_condition", "return_status", "return_images"]
|
||||||
|
widgets = {
|
||||||
|
"return_condition": forms.Textarea(
|
||||||
|
attrs={
|
||||||
|
"class": "oh-input oh-input--textarea oh-input--block",
|
||||||
|
"rows": 3,
|
||||||
|
"cols": 40,
|
||||||
|
"placeholder": _(
|
||||||
|
"on returns the laptop. However, it has suffered minor damage."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"return_status": forms.Select(
|
||||||
|
attrs={"class": "oh-select oh-select-2", "required": "true"},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Initializes the AssetReturnForm with initial values and custom field settings.
|
||||||
|
"""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields["return_date"].widget.attrs.update({"required": "true"})
|
||||||
|
self.fields["return_images"] = MultipleFileField(
|
||||||
|
label=_("Return Condition Images")
|
||||||
|
)
|
||||||
|
self.fields["return_images"].required = True
|
||||||
|
|
||||||
|
def clean_return_date(self):
|
||||||
|
"""
|
||||||
|
Validates the 'return_date' field.
|
||||||
|
|
||||||
|
Ensures that the return date is not in the future. If the return date is in the future,
|
||||||
|
a ValidationError is raised.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- The cleaned return date.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
- forms.ValidationError: If the return date is in the future.
|
||||||
|
"""
|
||||||
|
return_date = self.cleaned_data.get("return_date")
|
||||||
|
|
||||||
|
if return_date and return_date > date.today():
|
||||||
|
raise forms.ValidationError(_("Return date cannot be in the future."))
|
||||||
|
|
||||||
|
return return_date
|
||||||
|
|
||||||
|
|
||||||
|
class AssetBatchForm(ModelForm):
|
||||||
|
"""
|
||||||
|
A Django ModelForm for creating or updating AssetLot instances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Specifies the model and fields to be used for the AssetBatchForm.
|
||||||
|
Attributes:
|
||||||
|
model (class): The model class AssetLot to be used for the form.
|
||||||
|
fields (str): A special value "__all__" to include all fields
|
||||||
|
of the model in the form.
|
||||||
|
widgets (dict): A dictionary containing widget configurations for
|
||||||
|
specific form fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = AssetLot
|
||||||
|
fields = "__all__"
|
||||||
331
asset/models.py
Normal file
331
asset/models.py
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
"""
|
||||||
|
Models for Asset Management System
|
||||||
|
|
||||||
|
This module defines Django models to manage assets, their categories, assigning, and requests
|
||||||
|
within an Asset Management System.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import models
|
||||||
|
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, upload_path
|
||||||
|
|
||||||
|
|
||||||
|
class AssetCategory(HorillaModel):
|
||||||
|
"""
|
||||||
|
Represents a category for different types of assets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
asset_category_name = models.CharField(
|
||||||
|
max_length=255, unique=True, verbose_name=_("Name")
|
||||||
|
)
|
||||||
|
asset_category_description = models.TextField(
|
||||||
|
max_length=255, verbose_name=_("Description")
|
||||||
|
)
|
||||||
|
objects = models.Manager()
|
||||||
|
company_id = models.ManyToManyField(Company, blank=True, verbose_name=_("Company"))
|
||||||
|
objects = HorillaCompanyManager("company_id")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Meta class to add additional options
|
||||||
|
"""
|
||||||
|
|
||||||
|
verbose_name = _("Asset Category")
|
||||||
|
verbose_name_plural = _("Asset Categories")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.asset_category_name}"
|
||||||
|
|
||||||
|
|
||||||
|
class AssetLot(HorillaModel):
|
||||||
|
"""
|
||||||
|
Represents a lot associated with a collection of assets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
lot_number = models.CharField(
|
||||||
|
max_length=30,
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
unique=True,
|
||||||
|
verbose_name=_("Batch Number"),
|
||||||
|
)
|
||||||
|
lot_description = models.TextField(
|
||||||
|
null=True, blank=True, max_length=255, verbose_name=_("Description")
|
||||||
|
)
|
||||||
|
company_id = models.ManyToManyField(Company, blank=True, verbose_name=_("Company"))
|
||||||
|
objects = HorillaCompanyManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Meta class to add additional options
|
||||||
|
"""
|
||||||
|
|
||||||
|
ordering = ["-created_at"]
|
||||||
|
verbose_name = _("Asset Batch")
|
||||||
|
verbose_name_plural = _("Asset Batches")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.lot_number}"
|
||||||
|
|
||||||
|
|
||||||
|
class Asset(HorillaModel):
|
||||||
|
"""
|
||||||
|
Represents a asset with various attributes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ASSET_STATUS = [
|
||||||
|
("In use", _("In Use")),
|
||||||
|
("Available", _("Available")),
|
||||||
|
("Not-Available", _("Not-Available")),
|
||||||
|
]
|
||||||
|
asset_name = models.CharField(max_length=255, verbose_name=_("Asset Name"))
|
||||||
|
owner = models.ForeignKey(
|
||||||
|
Employee,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("Current User"),
|
||||||
|
)
|
||||||
|
asset_description = models.TextField(
|
||||||
|
null=True, blank=True, max_length=255, verbose_name=_("Description")
|
||||||
|
)
|
||||||
|
asset_tracking_id = models.CharField(
|
||||||
|
max_length=30, null=False, unique=True, verbose_name=_("Tracking Id")
|
||||||
|
)
|
||||||
|
asset_purchase_date = models.DateField(verbose_name=_("Purchase Date"))
|
||||||
|
asset_purchase_cost = models.DecimalField(
|
||||||
|
max_digits=10, decimal_places=2, verbose_name=_("Cost")
|
||||||
|
)
|
||||||
|
asset_category_id = models.ForeignKey(
|
||||||
|
AssetCategory, on_delete=models.PROTECT, verbose_name=_("Category")
|
||||||
|
)
|
||||||
|
asset_status = models.CharField(
|
||||||
|
choices=ASSET_STATUS,
|
||||||
|
default="Available",
|
||||||
|
max_length=40,
|
||||||
|
verbose_name=_("Status"),
|
||||||
|
)
|
||||||
|
asset_lot_number_id = models.ForeignKey(
|
||||||
|
AssetLot,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("Batch No"),
|
||||||
|
)
|
||||||
|
expiry_date = models.DateField(null=True, blank=True, verbose_name=_("Expiry Date"))
|
||||||
|
notify_before = models.IntegerField(
|
||||||
|
default=1, null=True, verbose_name=_("Notify Before (days)")
|
||||||
|
)
|
||||||
|
objects = HorillaCompanyManager("asset_category_id__company_id")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["-created_at"]
|
||||||
|
verbose_name = _("Asset")
|
||||||
|
verbose_name_plural = _("Assets")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.asset_name}-{self.asset_tracking_id}"
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
existing_asset = Asset.objects.filter(
|
||||||
|
asset_tracking_id=self.asset_tracking_id
|
||||||
|
).exclude(
|
||||||
|
id=self.pk
|
||||||
|
) # Exclude the current instance if updating
|
||||||
|
if existing_asset.exists():
|
||||||
|
raise ValidationError(
|
||||||
|
{
|
||||||
|
"asset_description": _(
|
||||||
|
"An asset with this tracking ID already exists."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return super().clean()
|
||||||
|
|
||||||
|
|
||||||
|
class AssetReport(HorillaModel):
|
||||||
|
"""
|
||||||
|
Model representing a report for an asset.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
- title: A CharField for the title of the report (optional).
|
||||||
|
- asset_id: A ForeignKey to the Asset model, linking the report to a specific asset.
|
||||||
|
"""
|
||||||
|
|
||||||
|
title = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
asset_id = models.ForeignKey(
|
||||||
|
Asset, related_name="asset_report", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
Returns a string representation of the AssetReport instance.
|
||||||
|
If a title is present, it returns "asset_id - title".
|
||||||
|
Otherwise, it returns "report for asset_id".
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
f"{self.asset_id} - {self.title}"
|
||||||
|
if self.title
|
||||||
|
else f"report for {self.asset_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AssetDocuments(HorillaModel):
|
||||||
|
"""
|
||||||
|
Model representing documents associated with an asset report.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
- asset_report: A ForeignKey to the AssetReport model, linking the document to
|
||||||
|
a specific asset report.
|
||||||
|
- file: A FileField for uploading the document file (optional).
|
||||||
|
"""
|
||||||
|
|
||||||
|
asset_report = models.ForeignKey(
|
||||||
|
"AssetReport", related_name="documents", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
file = models.FileField(upload_to=upload_path, blank=True, null=True)
|
||||||
|
objects = models.Manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Asset Document")
|
||||||
|
verbose_name_plural = _("Asset Documents")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"document for {self.asset_report}"
|
||||||
|
|
||||||
|
|
||||||
|
class ReturnImages(HorillaModel):
|
||||||
|
"""
|
||||||
|
Model representing images associated with a returned asset.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
- image: A FileField for uploading the image file (optional).
|
||||||
|
"""
|
||||||
|
|
||||||
|
image = models.FileField(upload_to=upload_path, blank=True, null=True)
|
||||||
|
|
||||||
|
|
||||||
|
class AssetAssignment(HorillaModel):
|
||||||
|
"""
|
||||||
|
Represents the allocation and return of assets to and from employees.
|
||||||
|
"""
|
||||||
|
|
||||||
|
STATUS = [
|
||||||
|
("Minor damage", _("Minor damage")),
|
||||||
|
("Major damage", _("Major damage")),
|
||||||
|
("Healthy", _("Healthy")),
|
||||||
|
]
|
||||||
|
asset_id = models.ForeignKey(
|
||||||
|
Asset, on_delete=models.PROTECT, verbose_name=_("Asset")
|
||||||
|
)
|
||||||
|
assigned_to_employee_id = models.ForeignKey(
|
||||||
|
Employee,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name="allocated_employee",
|
||||||
|
verbose_name=_("Assigned To"),
|
||||||
|
)
|
||||||
|
assigned_date = models.DateField(auto_now_add=True)
|
||||||
|
assigned_by_employee_id = models.ForeignKey(
|
||||||
|
Employee,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name="assigned_by",
|
||||||
|
verbose_name=_("Assigned By"),
|
||||||
|
)
|
||||||
|
return_date = models.DateField(null=True, blank=True, verbose_name=_("Return Date"))
|
||||||
|
return_condition = models.TextField(
|
||||||
|
null=True, blank=True, max_length=255, verbose_name=_("Return Condition")
|
||||||
|
)
|
||||||
|
return_status = models.CharField(
|
||||||
|
choices=STATUS,
|
||||||
|
max_length=30,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("Return Status"),
|
||||||
|
)
|
||||||
|
return_request = models.BooleanField(default=False)
|
||||||
|
objects = HorillaCompanyManager("asset_id__asset_lot_number_id__company_id")
|
||||||
|
return_images = models.ManyToManyField(
|
||||||
|
ReturnImages, blank=True, related_name="return_images"
|
||||||
|
)
|
||||||
|
assign_images = models.ManyToManyField(
|
||||||
|
ReturnImages,
|
||||||
|
blank=True,
|
||||||
|
related_name="assign_images",
|
||||||
|
verbose_name=_("Assign Condition Images"),
|
||||||
|
)
|
||||||
|
objects = HorillaCompanyManager(
|
||||||
|
"assigned_to_employee_id__employee_work_info__company_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class for AssetAssignment model"""
|
||||||
|
|
||||||
|
ordering = ["-id"]
|
||||||
|
verbose_name = _("Asset Allocation")
|
||||||
|
verbose_name_plural = _("Asset Allocations")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.assigned_to_employee_id} --- {self.asset_id} --- {self.return_status}"
|
||||||
|
|
||||||
|
|
||||||
|
class AssetRequest(HorillaModel):
|
||||||
|
"""
|
||||||
|
Represents a request for assets made by employees.
|
||||||
|
"""
|
||||||
|
|
||||||
|
STATUS = [
|
||||||
|
("Requested", _("Requested")),
|
||||||
|
("Approved", _("Approved")),
|
||||||
|
("Rejected", _("Rejected")),
|
||||||
|
]
|
||||||
|
requested_employee_id = models.ForeignKey(
|
||||||
|
Employee,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name="requested_employee",
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
verbose_name=_("Requesting User"),
|
||||||
|
)
|
||||||
|
asset_category_id = models.ForeignKey(
|
||||||
|
AssetCategory, on_delete=models.PROTECT, verbose_name=_("Asset Category")
|
||||||
|
)
|
||||||
|
asset_request_date = models.DateField(auto_now_add=True)
|
||||||
|
description = models.TextField(
|
||||||
|
null=True, blank=True, max_length=255, verbose_name=_("Description")
|
||||||
|
)
|
||||||
|
asset_request_status = models.CharField(
|
||||||
|
max_length=30, choices=STATUS, default="Requested", null=True, blank=True
|
||||||
|
)
|
||||||
|
objects = HorillaCompanyManager(
|
||||||
|
"requested_employee_id__employee_work_info__company_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class for AssetRequest model"""
|
||||||
|
|
||||||
|
ordering = ["-id"]
|
||||||
|
verbose_name = _("Asset Request")
|
||||||
|
verbose_name_plural = _("Asset Requests")
|
||||||
|
|
||||||
|
def status_html_class(self):
|
||||||
|
COLOR_CLASS = {
|
||||||
|
"Approved": "oh-dot--success",
|
||||||
|
"Requested": "oh-dot--info",
|
||||||
|
"Rejected": "oh-dot--danger",
|
||||||
|
}
|
||||||
|
|
||||||
|
LINK_CLASS = {
|
||||||
|
"Approved": "link-success",
|
||||||
|
"Requested": "link-info",
|
||||||
|
"Rejected": "link-danger",
|
||||||
|
}
|
||||||
|
status = self.asset_request_status
|
||||||
|
return {
|
||||||
|
"color": COLOR_CLASS.get(status),
|
||||||
|
"link": LINK_CLASS.get(status),
|
||||||
|
}
|
||||||
21
asset/resources.py
Normal file
21
asset/resources.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"""
|
||||||
|
Module: resources.py
|
||||||
|
This module defines classes for handling resources related to assets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from import_export import resources
|
||||||
|
|
||||||
|
from .models import Asset
|
||||||
|
|
||||||
|
|
||||||
|
class AssetResource(resources.ModelResource):
|
||||||
|
"""
|
||||||
|
This class is used to import and export Asset data using the import_export library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Specifies the model to be used for import and export.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = Asset
|
||||||
102
asset/scheduler.py
Normal file
102
asset/scheduler.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
"""
|
||||||
|
scheduler.py
|
||||||
|
|
||||||
|
This module is used to register scheduled tasks
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from notifications.signals import notify
|
||||||
|
|
||||||
|
|
||||||
|
def notify_expiring_assets():
|
||||||
|
"""
|
||||||
|
Finds all Expiring Assets and send a notification on the notify_before date.
|
||||||
|
"""
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from asset.models import Asset
|
||||||
|
|
||||||
|
today = date.today()
|
||||||
|
assets = Asset.objects.all()
|
||||||
|
|
||||||
|
# Cache bot & superuser once
|
||||||
|
bot = User.objects.filter(username="Horilla Bot").only("id").first()
|
||||||
|
superuser = User.objects.filter(is_superuser=True).only("id").first()
|
||||||
|
|
||||||
|
# Query only assets that are expiring today
|
||||||
|
assets = Asset.objects.filter(
|
||||||
|
expiry_date__isnull=False,
|
||||||
|
expiry_date__gte=today,
|
||||||
|
)
|
||||||
|
|
||||||
|
for asset in assets:
|
||||||
|
if asset.expiry_date:
|
||||||
|
expiry_date = asset.expiry_date
|
||||||
|
notify_date = expiry_date - timedelta(days=asset.notify_before)
|
||||||
|
recipient = getattr(asset.owner, "employee_user_id", None) or superuser
|
||||||
|
if notify_date == today and recipient:
|
||||||
|
notify.send(
|
||||||
|
bot,
|
||||||
|
recipient=recipient,
|
||||||
|
verb=f"The Asset '{asset.asset_name}' expires in {asset.notify_before} days",
|
||||||
|
verb_ar=f"تنتهي صلاحية الأصل '{asset.asset_name}' خلال {asset.notify_before} من الأيام",
|
||||||
|
verb_de=f"Das Asset {asset.asset_name} läuft in {asset.notify_before} Tagen ab.",
|
||||||
|
verb_es=f"El activo {asset.asset_name} caduca en {asset.notify_before} días.",
|
||||||
|
verb_fr=f"L'actif {asset.asset_name} expire dans {asset.notify_before} jours.",
|
||||||
|
redirect=reverse("asset-category-view"),
|
||||||
|
label="System",
|
||||||
|
icon="information",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def notify_expiring_documents():
|
||||||
|
"""
|
||||||
|
Finds all Expiring Documents and send a notification on the notify_before date.
|
||||||
|
"""
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from horilla_documents.models import Document
|
||||||
|
|
||||||
|
today = date.today()
|
||||||
|
documents = Document.objects.all()
|
||||||
|
bot = User.objects.filter(username="Horilla Bot").first()
|
||||||
|
for document in documents:
|
||||||
|
if document.expiry_date:
|
||||||
|
expiry_date = document.expiry_date
|
||||||
|
notify_date = expiry_date - timedelta(days=document.notify_before)
|
||||||
|
|
||||||
|
if notify_date == today:
|
||||||
|
notify.send(
|
||||||
|
bot,
|
||||||
|
recipient=document.employee_id.employee_user_id,
|
||||||
|
verb=f"The document ' {document.title} ' expires in {document.notify_before}\
|
||||||
|
days",
|
||||||
|
verb_ar=f"تنتهي صلاحية المستند '{document.title}' خلال {document.notify_before}\
|
||||||
|
يوم",
|
||||||
|
verb_de=f"Das Dokument '{document.title}' läuft in {document.notify_before}\
|
||||||
|
Tagen ab.",
|
||||||
|
verb_es=f"El documento '{document.title}' caduca en {document.notify_before}\
|
||||||
|
días",
|
||||||
|
verb_fr=f"Le document '{document.title}' expire dans {document.notify_before}\
|
||||||
|
jours",
|
||||||
|
redirect=reverse("asset-category-view"),
|
||||||
|
label="System",
|
||||||
|
icon="information",
|
||||||
|
)
|
||||||
|
if today >= expiry_date:
|
||||||
|
document.is_active = False
|
||||||
|
|
||||||
|
|
||||||
|
if not any(
|
||||||
|
cmd in sys.argv
|
||||||
|
for cmd in ["makemigrations", "migrate", "compilemessages", "flush", "shell"]
|
||||||
|
):
|
||||||
|
scheduler = BackgroundScheduler()
|
||||||
|
scheduler.add_job(notify_expiring_assets, "interval", days=1)
|
||||||
|
scheduler.add_job(notify_expiring_documents, "interval", hours=4)
|
||||||
|
scheduler.start()
|
||||||
59
asset/sidebar.py
Normal file
59
asset/sidebar.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
"""
|
||||||
|
assets/sidebar.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
MENU = _("Assets")
|
||||||
|
IMG_SRC = "images/ui/assets.svg"
|
||||||
|
|
||||||
|
SUBMENUS = [
|
||||||
|
{
|
||||||
|
"menu": _("Dashboard"),
|
||||||
|
"redirect": reverse("asset-dashboard"),
|
||||||
|
"accessibility": "asset.sidebar.dashboard_accessibility",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"menu": _("Asset View"),
|
||||||
|
"redirect": reverse("asset-category-view"),
|
||||||
|
"accessibility": "asset.sidebar.dashboard_accessibility",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"menu": _("Asset Batches"),
|
||||||
|
"redirect": reverse("asset-batch-view"),
|
||||||
|
"accessibility": "asset.sidebar.lot_accessibility",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"menu": _("Request and Allocation"),
|
||||||
|
"redirect": reverse("asset-request-allocation-view"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"menu": _("Asset History"),
|
||||||
|
"redirect": reverse("asset-history"),
|
||||||
|
"accessibility": "asset.sidebar.history_accessibility",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def dashboard_accessibility(request, submenu, user_perms, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Determine if the user has the necessary permissions to access the
|
||||||
|
dashboard and asset category view.
|
||||||
|
"""
|
||||||
|
return request.user.has_perm("asset.view_assetcategory")
|
||||||
|
|
||||||
|
|
||||||
|
def history_accessibility(request, submenu, user_perms, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Determine if the user has the necessary permissions to access the
|
||||||
|
dashboard and asset category view.
|
||||||
|
"""
|
||||||
|
return request.user.has_perm("asset.view_assetassignment")
|
||||||
|
|
||||||
|
|
||||||
|
def lot_accessibility(request, subment, user_perms, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Asset batch sidebar accessibility method
|
||||||
|
"""
|
||||||
|
return request.user.has_perm("asset.view_assetlot")
|
||||||
5
asset/tests.py
Normal file
5
asset/tests.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
"""
|
||||||
|
This module contains test cases for the assets application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
207
asset/urls.py
Normal file
207
asset/urls.py
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for asset-related views.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django import views
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from asset.forms import AssetCategoryForm, AssetForm
|
||||||
|
from asset.models import Asset, AssetCategory
|
||||||
|
from base.views import object_duplicate
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
"asset-creation/<int:asset_category_id>/",
|
||||||
|
views.asset_creation,
|
||||||
|
name="asset-creation",
|
||||||
|
),
|
||||||
|
path("asset-list/<int:cat_id>", views.asset_list, name="asset-list"),
|
||||||
|
path("asset-update/<int:asset_id>/", views.asset_update, name="asset-update"),
|
||||||
|
path(
|
||||||
|
"duplicate-asset/<int:obj_id>/",
|
||||||
|
object_duplicate,
|
||||||
|
name="duplicate-asset",
|
||||||
|
kwargs={
|
||||||
|
"model": Asset,
|
||||||
|
"form": AssetForm,
|
||||||
|
"form_name": "asset_creation_form",
|
||||||
|
"template": "asset/asset_creation.html",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
path("asset-delete/<int:asset_id>/", views.asset_delete, name="asset-delete"),
|
||||||
|
path(
|
||||||
|
"asset-information/<int:asset_id>/",
|
||||||
|
views.asset_information,
|
||||||
|
name="asset-information",
|
||||||
|
),
|
||||||
|
path("asset-category-view/", views.asset_category_view, name="asset-category-view"),
|
||||||
|
path(
|
||||||
|
"asset-category-view-search-filter",
|
||||||
|
views.asset_category_view_search_filter,
|
||||||
|
name="asset-category-view-search-filter",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-category-duplicate/<int:obj_id>/",
|
||||||
|
object_duplicate,
|
||||||
|
name="asset-category-duplicate",
|
||||||
|
kwargs={
|
||||||
|
"model": AssetCategory,
|
||||||
|
"form": AssetCategoryForm,
|
||||||
|
"form_name": "form",
|
||||||
|
"template": "category/asset_category_form.html",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-category-creation",
|
||||||
|
views.asset_category_creation,
|
||||||
|
name="asset-category-creation",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-category-update/<int:cat_id>",
|
||||||
|
views.asset_category_update,
|
||||||
|
name="asset-category-update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-category-delete/<int:cat_id>",
|
||||||
|
views.delete_asset_category,
|
||||||
|
name="asset-category-delete",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-request-creation",
|
||||||
|
views.asset_request_creation,
|
||||||
|
name="asset-request-creation",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-request-allocation-view/",
|
||||||
|
views.asset_request_allocation_view,
|
||||||
|
name="asset-request-allocation-view",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-request-individual-view/<int:asset_request_id>",
|
||||||
|
views.asset_request_individual_view,
|
||||||
|
name="asset-request-individual-view",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"own-asset-individual-view/<int:asset_id>",
|
||||||
|
views.own_asset_individual_view,
|
||||||
|
name="own-asset-individual-view",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-allocation-individual-view/<int:asset_allocation_id>",
|
||||||
|
views.asset_allocation_individual_view,
|
||||||
|
name="asset-allocation-individual-view",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-request-allocation-view-search-filter",
|
||||||
|
views.asset_request_alloaction_view_search_filter,
|
||||||
|
name="asset-request-allocation-view-search-filter",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-request-approve/<int:req_id>/",
|
||||||
|
views.asset_request_approve,
|
||||||
|
name="asset-request-approve",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-request-reject/<int:req_id>/",
|
||||||
|
views.asset_request_reject,
|
||||||
|
name="asset-request-reject",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-allocate-creation",
|
||||||
|
views.asset_allocate_creation,
|
||||||
|
name="asset-allocate-creation",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-allocate-return/<int:asset_id>/",
|
||||||
|
views.asset_allocate_return,
|
||||||
|
name="asset-allocate-return",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-allocate-return-request/<int:asset_id>/",
|
||||||
|
views.asset_allocate_return_request,
|
||||||
|
name="asset-allocate-return-request",
|
||||||
|
),
|
||||||
|
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-search",
|
||||||
|
views.asset_batch_number_search,
|
||||||
|
name="asset-batch-number-search",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-batch-update/<int:batch_id>",
|
||||||
|
views.asset_batch_update,
|
||||||
|
name="asset-batch-update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-batch-number-delete/<int:batch_id>",
|
||||||
|
views.asset_batch_number_delete,
|
||||||
|
name="asset-batch-number-delete",
|
||||||
|
),
|
||||||
|
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/<int:asset_id>",
|
||||||
|
views.add_asset_report,
|
||||||
|
name="add-asset-report",
|
||||||
|
),
|
||||||
|
path("dashboard/", views.asset_dashboard, name="asset-dashboard"),
|
||||||
|
path(
|
||||||
|
"asset-dashboard-requests/",
|
||||||
|
views.asset_dashboard_requests,
|
||||||
|
name="asset-dashboard-requests",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-dashboard-allocates/",
|
||||||
|
views.asset_dashboard_allocates,
|
||||||
|
name="asset-dashboard-allocates",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-available-chart/",
|
||||||
|
views.asset_available_chart,
|
||||||
|
name="asset-available-chart",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-category-chart/", views.asset_category_chart, name="asset-category-chart"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-history",
|
||||||
|
views.asset_history,
|
||||||
|
name="asset-history",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-history-single-view/<int:asset_id>",
|
||||||
|
views.asset_history_single_view,
|
||||||
|
name="asset-history-single-view",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-history-search",
|
||||||
|
views.asset_history_search,
|
||||||
|
name="asset-history-search",
|
||||||
|
),
|
||||||
|
path("asset-tab/<int:emp_id>", views.asset_tab, name="asset-tab"),
|
||||||
|
path(
|
||||||
|
"profile-asset-tab/<int:emp_id>",
|
||||||
|
views.profile_asset_tab,
|
||||||
|
name="profile-asset-tab",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"asset-request-tab/<int:emp_id>",
|
||||||
|
views.asset_request_tab,
|
||||||
|
name="asset-request-tab",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"main-dashboard-asset-requests",
|
||||||
|
views.asset_dashboard_requests,
|
||||||
|
name="main-dashboard-asset-requests",
|
||||||
|
),
|
||||||
|
]
|
||||||
1878
asset/views.py
Normal file
1878
asset/views.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user