[ADD] ASSET: Asset history tracking

This commit is contained in:
Horilla
2024-02-07 13:00:09 +05:30
parent 41960e36ce
commit b763d44e97
14 changed files with 1067 additions and 12 deletions

View File

@@ -230,3 +230,68 @@ class AssetAllocationReGroup:
("assigned_date", "Assigned Date"), ("assigned_date", "Assigned Date"),
("return_date", "Return 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):
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"),
]

View File

@@ -11,6 +11,7 @@ from django import forms
from base.forms import ModelForm from base.forms import ModelForm
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from employee.forms import MultipleFileField
from employee.models import Employee from employee.models import Employee
from asset.models import Asset, AssetDocuments, AssetReport, AssetRequest, AssetAssignment, AssetCategory, AssetLot from asset.models import Asset, AssetDocuments, AssetReport, AssetRequest, AssetAssignment, AssetCategory, AssetLot
from base.methods import reload_queryset from base.methods import reload_queryset
@@ -255,6 +256,9 @@ class AssetAllocationForm(ModelForm):
self.fields["asset_id"].queryset = Asset.objects.filter( self.fields["asset_id"].queryset = Asset.objects.filter(
asset_status="Available" asset_status="Available"
) )
self.fields["assign_images"] = MultipleFileField()
self.fields["assign_images"].required = True
class Meta: class Meta:
""" """
@@ -269,7 +273,7 @@ class AssetAllocationForm(ModelForm):
model = AssetAssignment model = AssetAssignment
fields = "__all__" fields = "__all__"
exclude = ["return_date", "return_condition", "assigned_date"] exclude = ["return_date", "return_condition", "assigned_date", "return_images"]
widgets = { widgets = {
"asset_id": forms.Select(attrs={"class": "oh-select oh-select-2 "}), "asset_id": forms.Select(attrs={"class": "oh-select oh-select-2 "}),
"assigned_to_employee_id": forms.Select( "assigned_to_employee_id": forms.Select(
@@ -300,7 +304,7 @@ class AssetReturnForm(ModelForm):
""" """
model = AssetAssignment model = AssetAssignment
fields = ["return_date", "return_condition", "return_status"] fields = ["return_date", "return_condition", "return_status", "return_images"]
widgets = { widgets = {
"return_date": forms.DateInput( "return_date": forms.DateInput(
attrs={"type": "date", "class": "oh-input w-100", "required": "true"} attrs={"type": "date", "class": "oh-input w-100", "required": "true"}
@@ -324,6 +328,8 @@ class AssetReturnForm(ModelForm):
super(AssetReturnForm, self).__init__(*args, **kwargs) super(AssetReturnForm, self).__init__(*args, **kwargs)
self.fields["return_date"].initial = date.today() self.fields["return_date"].initial = date.today()
self.fields["return_images"] = MultipleFileField(label="Images")
self.fields["return_images"].required = True
def clean_return_date(self): def clean_return_date(self):
return_date = self.cleaned_data.get("return_date") return_date = self.cleaned_data.get("return_date")

View File

@@ -103,6 +103,9 @@ class AssetDocuments(models.Model):
return f'document for {self.asset_report}' return f'document for {self.asset_report}'
class ReturnImages(models.Model):
image = models.FileField(upload_to="asset/return_images/", blank=True, null=True)
class AssetAssignment(models.Model): class AssetAssignment(models.Model):
""" """
Represents the allocation and return of assets to and from employees. Represents the allocation and return of assets to and from employees.
@@ -130,6 +133,11 @@ class AssetAssignment(models.Model):
) )
return_request = models.BooleanField(default = False) return_request = models.BooleanField(default = False)
objects = HorillaCompanyManager("asset_id__asset_lot_number_id__company_id") 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")
def __str__(self):
return f"{self.assigned_to_employee_id} --- {self.asset_id} --- {self.return_status}"
class AssetRequest(models.Model): class AssetRequest(models.Model):

View File

@@ -1,6 +1,6 @@
{% load i18n %} {% load i18n %}
<div class="oh-modal__dialog-title">{% trans "Asset Return Form" %}</div> <div class="oh-modal__dialog-title">{% trans "Asset Return Form" %}</div>
<form hx-post="{%url 'asset-allocate-return' asset_id=asset_id %}" hx-target="#asset-request-allocation-modal-target" method="post"> <form hx-post="{%url 'asset-allocate-return' asset_id=asset_id %}" hx-target="#asset-request-allocation-modal-target" method="post" hx-encoding="multipart/form-data">
{% csrf_token %} {% csrf_token %}
<div class="m-3"> <div class="m-3">
<div class="oh-input__group "> <div class="oh-input__group ">
@@ -16,6 +16,10 @@
<label class="oh-input__label" for="objective">{% trans "Return Condition" %}</label> <label class="oh-input__label" for="objective">{% trans "Return Condition" %}</label>
{{asset_return_form.return_condition}} {{asset_return_form.return_condition}}
</div> </div>
<div class="oh-input__group ">
<label class="oh-input__label" for="objective">{% trans "Return Condition Images" %}</label>
{{asset_return_form.return_images}}
</div>
</div> </div>
<div class="oh-modal__dialog-footer justify-content-between mt-3"> <div class="oh-modal__dialog-footer justify-content-between mt-3">
<button <button

View File

@@ -0,0 +1,115 @@
{% load static %} {% load i18n %} {% load mathfilters %} {% load widget_tweaks %}
<div class="oh-dropdown__filter-body">
<div class="oh-accordion">
<div class="oh-accordion-header">{% trans "Asset History" %}</div>
<div class="oh-accordion-body">
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group">
<label
class="oh-label"
for="{{f.form.assigned_to_employee_id.id_for_label}}"
>{% trans "Allocated User" %}</label
>
{{f.form.assigned_to_employee_id}}
</div>
<div class="oh-input-group">
<label
class="oh-label"
for="{{f.form.asset_id.id_for_label}}"
>{% trans "Asset" %}</label
>
{{f.form.asset_id}}
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group">
<label
class="oh-label"
for="{{f.form.assigned_date.id_for_label}}"
>{% trans "Asset Allocated Date" %}</label
>
{{ f.form.assigned_date |attr:"type:date" }}
</div>
<div class="oh-input-group">
<label
class="oh-label"
for="{{f.form.return_status.id_for_label}}"
>{% trans "Status" %}</label
>
{{f.form.return_status}}
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group">
<label
class="oh-label"
for="{{f.form.return_date.id_for_label}}"
>{% trans "Return Date" %}</label
>
{{ f.form.return_date|attr:"type:date" }}
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group">
<label
class="oh-label"
for="{{f.form.assigned_by_employee_id.id_for_label}}"
>{% trans "Allocated By" %}</label
>
{{f.form.assigned_by_employee_id}}
</div>
</div>
</div>
</div>
</div><div class="oh-accordion">
<div class="oh-accordion-header">{% trans "Advanced" %}</div>
<div class="oh-accordion-body">
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group">
<label
class="oh-label"
for="{{f.form.return_date_gte.id_for_label}}"
>{% trans "Return Date Greater Or Equal" %}</label
>
{{f.form.return_date_gte}}
</div>
<div class="oh-input-group">
<label
class="oh-label"
for="{{f.form.return_date_lte.id_for_label}}"
>{% trans "Assign Date Greater Or Equal" %}</label
>
{{f.form.assigned_date_gte}}
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group">
<label
class="oh-label"
for="{{f.form.return_date_gte.id_for_label}}"
>{% trans "Return Date lesser Or Equal" %}</label
>
{{f.form.return_date_lte}}
</div>
<div class="oh-input-group">
<label
class="oh-label"
for="{{f.form.return_date_lte.id_for_label}}"
>{% trans "Assign Date Lesser Or Equal" %}</label
>
{{f.form.assigned_date_lte}}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="oh-dropdown__filter-footer">
<button class="oh-btn oh-btn--secondary oh-btn--small w-100 filterButton">
{% trans "Filter" %}
</button>
</div>
<script src="{% static '/base/filter.js' %}"></script>

View File

@@ -0,0 +1,141 @@
{% load i18n %}
{% load static %}
{% load basefilters %}
{% include 'filter_tags.html' %}
<div class="oh-table_sticky--wrapper" id="historyTable">
<div class="oh-sticky-table">
<div class="oh-sticky-table__table">
<div class="oh-sticky-table__thead" >
<div class="oh-sticky-table__tr">
<div class="oh-sticky-table__th {% if request.sort_option.order == '-asset_id__asset_name' %}arrow-up {% elif request.sort_option.order == 'asset_id__asset_name' %}arrow-down {% else %}arrow-up-down {% endif %}" hx-get="{% url 'asset-history-search' %}?{{pd}}&sortby=asset_id__asset_name" hx-target="#historyTable">{% trans "Asset" %}</div>
<div class="oh-sticky-table__th {% if request.sort_option.order == '-assigned_to_employee_id__employee_first_name' %}arrow-up {% elif request.sort_option.order == 'assigned_to_employee_id__employee_first_name' %}arrow-down {% else %}arrow-up-down {% endif %}" hx-get="{% url 'asset-history-search' %}?{{pd}}&sortby=assigned_to_employee_id__employee_first_name" hx-target="#historyTable">{% trans "Employee" %}</div>
<div class="oh-sticky-table__th {% if request.sort_option.order == '-assigned_date' %}arrow-up {% elif request.sort_option.order == 'assigned_date' %}arrow-down {% else %}arrow-up-down {% endif %}" hx-get="{% url 'asset-history-search' %}?{{pd}}&sortby=assigned_date" hx-target="#historyTable">{% trans "Assigned Date" %}</div>
<div class="oh-sticky-table__th {% if request.sort_option.order == '-return_date' %}arrow-up {% elif request.sort_option.order == 'return_date' %}arrow-down {% else %}arrow-up-down {% endif %}" hx-get="{% url 'asset-history-search' %}?{{pd}}&sortby=return_date" hx-target="#historyTable">{% trans "Returned Date" %}</div>
<div class="oh-sticky-table__th">{% trans "Return Status" %}</div>
</div>
</div>
<div class="oh-sticky-table__tbody">
{% for asset_assignement in asset_assignments %}
<div class="oh-sticky-table__tr" hx-get='{% url "asset-history-single-view" asset_assignement.id %}' hx-target="#historySingleModalBody" data-toggle="oh-modal-toggle", data-target= "#historySingleModal" >
<div class="oh-sticky-table__sd">
<div class="oh-profile oh-profile--md">
<div class="oh-profile__avatar mr-1">
<img
src="https://ui-avatars.com/api/?name={{asset_assignement.asset_id.asset_name}}&background=random"
class="oh-profile__image"
alt=""
/>
</div>
<span class="oh-profile__name oh-text--dark">{{asset_assignement.asset_id}}</span>
</div>
</div>
<div class="oh-sticky-table__td">
{{asset_assignement.assigned_to_employee_id}}
</div>
<div class="oh-sticky-table__td">{{asset_assignement.assigned_date}} </div>
<div class="oh-sticky-table__td">{{asset_assignement.return_date}}</div>
<div class="oh-sticky-table__td">{{asset_assignement.return_status}}</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="oh-pagination">
<span class="oh-pagination__page">
{% trans "Page" %} {{ asset_assignments.number }} {% trans "of" %} {{ asset_assignments.paginator.num_pages }}.
</span>
<nav class="oh-pagination__nav">
<div class="oh-pagination__input-container me-3">
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
<input
type="number"
name="page"
class="oh-pagination__input"
value="{{asset_assignments.number}}"
hx-get="{% url 'asset-history-search' %}?{{pd}}"
hx-target="#historyTable"
min="1"
/>
<span class="oh-pagination__label">{% trans "of" %} {{asset_assignments.paginator.num_pages}}</span>
</div>
<ul class="oh-pagination__items">
{% if asset_assignments.has_previous %}
<li class="oh-pagination__item oh-pagination__item--wide">
<a hx-target='#historyTable' hx-get="{% url 'asset-history-search' %}?{{pd}}&page=1" class="oh-pagination__link" onclick="tickCheckboxes();">{% trans "First" %}</a>
</li>
<li class="oh-pagination__item oh-pagination__item--wide">
<a hx-target='#historyTable' hx-get="{% url 'asset-history-search' %}?{{pd}}&page={{ asset_assignments.previous_page_number }}" class="oh-pagination__link" onclick="tickCheckboxes();">{% trans "Previous" %}</a>
</li>
{% endif %}
{% if asset_assignments.has_next %}
<li class="oh-pagination__item oh-pagination__item--wide">
<a hx-target='#historyTable' hx-get="{% url 'asset-history-search' %}?{{pd}}&page={{ asset_assignments.next_page_number }}" class="oh-pagination__link" onclick="tickCheckboxes();">{% trans "Next" %}</a>
</li>
<li class="oh-pagination__item oh-pagination__item--wide">
<a hx-target='#historyTable' hx-get="{% url 'asset-history-search' %}?{{pd}}&page={{ asset_assignments.paginator.num_pages }}" class="oh-pagination__link" onclick="tickCheckboxes();">{% trans "Last" %}</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
<div
class="oh-modal"
id="historySingleModal"
role="dialog"
aria-labelledby="historySingleModal"
aria-hidden="true"
>
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<h2 class="oh-modal__dialog-title" id="historySingleModalLabel">
{% trans "Asset Details" %}
</h2>
<button class="oh-modal__close" aria-label="Close" >
<ion-icon name="close-outline"></ion-icon>
</button>
<div class="oh-modal__dialog-body p-0 pt-2" id='historySingleModalBody'></div>
</div>
</div>
</div>
</div>
<script>
// This lines is used to set default selected stage for exits lines
function enlargeImage(src,$element) {
$("#enlargeImageContainer").empty()
var enlargeImageContainer = $("#enlargeImageContainer")
enlargeImageContainer.empty()
style = 'width:100%; height:90%; box-shadow: 0 10px 10px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); background:white'
var enlargedImage = $('<iframe>').attr({ src: src, style: style })
var name = $('<span>').text(src.split('/').pop().replace(/_/g, ' '))
enlargeImageContainer.append(enlargedImage)
enlargeImageContainer.append(name)
setTimeout(function () {
enlargeImageContainer.show()
const iframe = document.querySelector('iframe').contentWindow
var iframe_document = iframe.document
iframe_image = iframe_document.getElementsByTagName('img')[0]
$(iframe_image).attr('style', 'width:100%; height:100%;')
}, 100)
}
function hideEnlargeImage() {
var enlargeImageContainer = $('#enlargeImageContainer')
enlargeImageContainer.empty()
}
$(document).on('click', function (event) {
if (!$(event.target).closest('#enlargeImageContainer').length) {
hideEnlargeImage()
}
})
function submitForm(elem) {
$(elem).siblings(".add_more_submit").click();
}
</script>

View File

@@ -0,0 +1,159 @@
{% load i18n %}
{% load basefilters %}
{% if perms.attendance.add_attendanceovertime or request.user|is_reportingmanager %}
<div
class="oh-modal"
id="assetHistory"
role="dialog"
aria-labelledby="assetHistory"
aria-hidden="true"
>
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<h2 class="oh-modal__dialog-title" id="assetHistoryLavel">
{% trans "Export Hour Accounts" %}
</h2>
<button class="oh-modal__close" aria-label="Close">
<ion-icon name="close-outline"></ion-icon>
</button>
<div
class="oh-modal__dialog-body p-0 pt-2"
id="assetHistoryModalBody"
>
{% comment %} <form
action="{%url 'attendance-account-info-export' %}"
method="get"
onsubmit="event.stopPropagation();$(this).parents().find('.oh-modal--show').last().toggleClass('oh-modal--show');"
id="assetHistoryForm"
>
{% csrf_token %} {% include 'attendance/attendance_account/attendance_account_export_filter.html'%}
<div class="oh-dropdown__filter-footer">
<button class="oh-btn oh-btn--secondary oh-btn--small w-100">
{% trans "Export" %}
</button>
</div>
</form> {% endcomment %}
</div>
</div>
</div>
</div>
{% endif %}
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
<div class="oh-main__titlebar oh-main__titlebar--left">
<h1 class="oh-main__titlebar-title fw-bold">
<a href="{% url 'asset-history' %}" class='text-dark'>
{% trans "Asset History" %}
</a>
</h1>
<a
class="oh-main__titlebar-search-toggle"
role="button"
aria-label="Toggle Search"
@click="searchShow = !searchShow"
>
<ion-icon
name="search-outline"
class="oh-main__titlebar-serach-icon"
></ion-icon>
</a>
</div>
<div class="oh-main__titlebar oh-main__titlebar--right">
<div
class="oh-input-group oh-input__search-group"
:class="searchShow ? 'oh-input__search-group--show' : ''"
>
<ion-icon
name="search-outline"
class="oh-input-group__icon oh-input-group__icon--left"
></ion-icon>
<input
type="text"
class="oh-input oh-input__icon"
aria-label="Search Input"
id="attendance-search"
name='search'
placeholder="{% trans 'Search' %}"
hx-get="{% url 'asset-history-search' %}"
hx-trigger="keyup changed delay:500ms, search"
hx-target="#historyTable"
hx-swap="innerHTML"
/>
</div>
<div class="oh-main__titlebar-button-container">
<form
hx-get="{% url 'asset-history-search' %}"
hx-swap="innerHTML"
hx-target="#historyTable"
id="filterForm"
class="d-flex"
>
<div class="oh-dropdown" x-data="{open: false}">
<button class="oh-btn ml-2" @click="open = !open" onclick="event.preventDefault()">
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}<div id="filterCount"></div>
</button>
<div
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
x-show="open"
@click.outside="open = false"
style="display: none;
width: 550px;"
>
{% include 'asset_history/asset_history_filter.html' %}
</div>
</div>
<div class="oh-dropdown" x-data="{open: false}">
<button class="oh-btn ml-2" @click="open = !open" onclick="event.preventDefault()">
<ion-icon name="library-outline" class="mr-1"></ion-icon>{% trans "Group By" %}
<div id="filterCount"></div>
</button>
<div
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
x-show="open"
@click.outside="open = false"
style="display: none"
>
<div class="oh-accordion">
<label for="id_field">{% trans "Group By" %}</label>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group">
<label class="oh-label" for="id_field">{% trans "Field" %}</label>
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group">
<select
class="oh-select mt-1 w-100"
id="id_field"
name="field"
class="select2-selection select2-selection--single"
>
{% for field in gp_fields %}
<option value="{{ field.0 }}">{% trans field.1 %}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
<script>
$('#attendance-search').keydown(function (e) {
var val = $(this).val();
$('.pg').attr('hx-vals', `{"search":${val}}`);
});
$(document).ready(function(){
$('#id_field').on('change',function(){
$('.filterButton')[0].click();
})
});
</script>
</section>

View File

@@ -0,0 +1,153 @@
{% load static %} {% load i18n %}
{% comment %} <div class="oh-modal__dialog oh-modal__dialog--navigation m-0 p-0">
<button
hx-get="{% url 'asset-allocation-individual-view' previous %}?allocations_ids={{allocations_ids}}"
hx-target="#singleAllocate"
class="oh-modal__diaglog-nav oh-modal__nav-prev"
>
<ion-icon
name="chevron-back-outline"
class="md hydrated"
role="img"
aria-label="chevron back outline"
></ion-icon>
</button>
<button
hx-get="{% url 'asset-allocation-individual-view' next %}?allocations_ids={{allocations_ids}}"
hx-target="#singleAllocate"
class="oh-modal__diaglog-nav oh-modal__nav-next"
>
<ion-icon
name="chevron-forward-outline"
class="md hydrated"
role="img"
aria-label="chevron forward outline"
></ion-icon>
</button>
</div> {% endcomment %}
<a class="oh-timeoff-modal__profile-content pt-4" style="text-decoration:none;"
href ="">
<div class="oh-profile mb-2">
<div class="oh-profile__avatar">
<img
src="{{asset_assignment.assigned_to_employee_id.get_avatar}}"
class="oh-profile__image me-2"
alt="Mary Magdalene"
/>
</div>
<div class="oh-timeoff-modal__profile-info">
<span class="oh-timeoff-modal__user fw-bold m-0"
>{{asset_assignment.asset_id}}</span
>
<span
class="oh-timeoff-modal__user m-0"
style="font-size: 18px; color: #4d4a4a"
>
{{asset_assignment.asset_id.asset_category_id}} </span
>
</div>
</div>
</a>
<div class="oh-timeoff-modal__stats-container">
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title"
>{% trans "Allocated User" %}</span
>
<span class="oh-timeoff-modal__stat-count"
>{{asset_assignment.assigned_to_employee_id}}</span
>
</div>
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title"
>{% trans "Returned Status" %}
</span>
<span class="oh-timeoff-modal__stat-count"
>{{asset_assignment.return_status}}</span
>
</div>
</div>
<div class="oh-timeoff-modal__stats-container mt-3">
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title"
>{% trans "Allocated Date" %}
</span>
<span class="oh-timeoff-modal__stat-count dateformat_changer"
>{{asset_assignment.assigned_date}}</span
>
</div>
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title"
>{% trans "Returned Date" %}
</span>
<span class="oh-timeoff-modal__stat-count dateformat_changer"
>{{asset_assignment.return_date}}</span
>
</div>
</div>
<div class="oh-timeoff-modal__stats-container mt-3">
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title">{% trans "Asset" %}</span>
<span class="oh-timeoff-modal__stat-count"
>{{asset_assignment.asset_id}}</span
>
</div>
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title"
>{% trans "Return Description" %}</span
>
<div class="oh-timeoff-modal__stat-description">
{{asset_assignment.return_condition}}
</div>
</div>
</div>
<div class="oh-timeoff-modal__stats-container mt-3 ">
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title">{% trans "Assign Condition Images" %}</span>
<span class="oh-timeoff-modal__stat-count"
>
<div class="d-flex mt-2 mb-2">
{% for doc in asset_assignment.assign_images.all %}
<a
href="{{doc.image.url}}"
rel="noopener noreferrer"
target="_blank"
><span
class="oh-file-icon oh-file-icon--pdf"
onmouseover="enlargeImage('{{doc.image.url}}')"
style="width:40px;height:40px"
></span
></a>
{% endfor %}
</div>
</span>
</div>
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title"
>{% trans "Return Condition Images" %}</span
>
<div class="oh-timeoff-modal__stat-description">
<div class="d-flex ">
{% for doc in asset_assignment.return_images.all %}
<a
href="{{doc.image.url}}"
rel="noopener noreferrer"
target="_blank"
><span
class="oh-file-icon oh-file-icon--pdf"
onmouseover="enlargeImage('{{doc.image.url}}')"
style="width:40px;height:40px"
></span
></a>
{% endfor %}
</div>
</div>
</div>
</div>
<div class="m-3 " id="enlargeImageContainer"></div>

View File

@@ -0,0 +1,13 @@
{% extends 'index.html' %}{% block content %}
{% load static %}{% load i18n %}
{% include 'asset_history/asset_history_nav.html' %}
<div class="oh-checkpoint-badge mb-2" id="selectedInstances" data-ids="[]" data-clicked="" style="display:none;" >
{% trans "Selected Assets" %}
</div>
<div class="oh-wrapper">
{% include 'asset_history/asset_history_list.html' %}
</div>
{% endblock %}

View File

@@ -0,0 +1,272 @@
{% load attendancefilters %} {% load basefilters %} {% load static %}
{% load i18n %} {% include 'filter_tags.html' %}
<div class="oh-card">
{% for asset_history_list in asset_assignments %}
<div class="oh-accordion-meta">
<div class="oh-accordion-meta__item">
<div
class="oh-accordion-meta__header"
onclick='$(this).toggleClass("oh-accordion-meta__header--show");'
>
<span class="oh-accordion-meta__title pt-3 pb-3">
<div class="oh-tabs__input-badge-container">
<span
class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1"
>
{{asset_history_list.list|length}}
</span>
{{asset_history_list.grouper}}
</div>
</span>
</div>
<div class="oh-accordion-meta__body d-none">
<div class="oh-sticky-table oh-sticky-table--no-overflow mb-5">
<div class="oh-sticky-table">
<div class="oh-sticky-table__table">
<div class="oh-sticky-table__thead">
<div class="oh-sticky-table__tr">
<div
class="oh-sticky-table__th"
hx-get=""
hx-target="#historyTable"
>
{% trans "Asset" %}
</div>
<div
class="oh-sticky-table__th {% if request.sort_option.order == '-assigned_to_employee_id__employee_first_name' %}arrow-up {% elif request.sort_option.order == 'assigned_to_employee_id__employee_first_name' %}arrow-down {% else %}arrow-up-down {% endif %}"
hx-get=""
hx-target="#historyTable"
>
{% trans "Employee" %}
</div>
<div
class="oh-sticky-table__th"
hx-get=""
hx-target="#historyTable"
>
{% trans "Assigned Date" %}
</div>
<div
class="oh-sticky-table__th"
hx-get=""
hx-target="#historyTable"
>
{% trans "Returned Date" %}
</div>
<div
class="oh-sticky-table__th"
hx-get=""
hx-target="#historyTable"
>
{% trans "Return Status" %}
</div>
</div>
</div>
<div class="oh-sticky-table__tbody">
{% for asset_assignement in asset_history_list.list %}
<div
class="oh-sticky-table__tr"
hx-get='{% url "asset-history-single-view" asset_assignement.id %}'
hx-target="#historySingleModalBody"
data-toggle="oh-modal-toggle"
,
data-target="#historySingleModal"
>
<div class="oh-sticky-table__sd">
<div class="oh-profile oh-profile--md">
<div
class="oh-profile__avatar mr-1"
>
<img
src="https://ui-avatars.com/api/?name={{asset_assignement.asset_id.asset_name}}&background=random"
class="oh-profile__image"
alt=""
/>
</div>
<span
class="oh-profile__name oh-text--dark"
>{{asset_assignement.asset_id}}</span
>
</div>
</div>
<div class="oh-sticky-table__td">
{{asset_assignement.assigned_to_employee_id}}
</div>
<div class="oh-sticky-table__td">
{{asset_assignement.assigned_date}}
</div>
<div class="oh-sticky-table__td">
{{asset_assignement.return_date}}
</div>
<div class="oh-sticky-table__td">
{{asset_assignement.return_status}}
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
<div class="oh-pagination">
<span class="oh-pagination__page">
{% trans "Page" %} {{ asset_history_list.list.number }}
{%trans "of" %} {{asset_history_list.list.paginator.num_pages }}.
</span>
<nav class="oh-pagination__nav">
<div class="oh-pagination__input-container me-3">
<span class="oh-pagination__label me-1"
>{% trans "Page" %}</span
>
<input
type="number"
name="{{asset_history_list.dynamic_name}}"
class="oh-pagination__input"
value="{{asset_history_list.list.number}}"
hx-get="{% url 'asset-history-search' %}?{{pd}}"
hx-target="#historyTable"
min="1"
/>
<span class="oh-pagination__label"
>{% trans "of" %}
{{asset_history_list.list.paginator.num_pages}}</span
>
</div>
<ul class="oh-pagination__items">
{% if asset_history_list.list.has_previous %}
<li
class="oh-pagination__item oh-pagination__item--wide"
>
<a
hx-target="#historyTable"
hx-get="{% url 'asset-history-search' %}?{{pd}}&{{asset_history_list.dynamic_name}}=1"
class="oh-pagination__link"
>{% trans "First" %}</a
>
</li>
<li
class="oh-pagination__item oh-pagination__item--wide"
>
<a
hx-target="#historyTable"
hx-get="{% url 'asset-history-search' %}?{{pd}}&{{asset_history_list.dynamic_name}}={{ asset_history_list.list.previous_page_number }}"
class="oh-pagination__link"
>{% trans "Previous" %}</a
>
</li>
{% endif %} {% if asset_history_list.list.has_next %}
<li
class="oh-pagination__item oh-pagination__item--wide"
>
<a
hx-target="#historyTable"
hx-get="{% url 'asset-history-search' %}?{{pd}}&{{asset_history_list.dynamic_name}}={{ asset_history_list.list.next_page_number }}"
class="oh-pagination__link"
>{% trans "Next" %}</a
>
</li>
<li
class="oh-pagination__item oh-pagination__item--wide"
>
<a
hx-target="#historyTable"
hx-get="{% url 'asset-history-search' %}?{{pd}}&{{asset_history_list.dynamic_name}}={{ asset_history_list.list.paginator.num_pages }}"
class="oh-pagination__link"
>{% trans "Last" %}</a
>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
</div>
</div>
{% endfor %}
<div class="oh-pagination">
<span class="oh-pagination__page">
{% trans "Page" %} {{ asset_assignments.number }} {% trans "of" %}
{{ asset_assignments.paginator.num_pages }}.
</span>
<nav class="oh-pagination__nav">
<div class="oh-pagination__input-container me-3">
<span class="oh-pagination__label me-1"
>{% trans "Page" %}</span
>
<input
type="number"
name="page"
class="oh-pagination__input"
value="{{asset_assignments.number}}"
hx-get="{% url 'asset-history-search' %}?{{pd}}"
hx-target="#historyTable"
min="1"
/>
<span class="oh-pagination__label"
>{% trans "of" %}
{{asset_assignments.paginator.num_pages}}</span
>
</div>
<ul class="oh-pagination__items">
{% if asset_assignments.has_previous %}
<li class="oh-pagination__item oh-pagination__item--wide">
<a
hx-target="#historyTable"
hx-get="{% url 'asset-history-search' %}?{{pd}}&page=1"
class="oh-pagination__link"
>{% trans "First" %}</a
>
</li>
<li class="oh-pagination__item oh-pagination__item--wide">
<a
hx-target="#historyTable"
hx-get="{% url 'asset-history-search' %}?{{pd}}&page={{ asset_assignments.previous_page_number }}"
class="oh-pagination__link"
>{% trans "Previous" %}</a
>
</li>
{% endif %} {% if asset_assignments.has_next %}
<li class="oh-pagination__item oh-pagination__item--wide">
<a
hx-target="#historyTable"
hx-get="{% url 'asset-history-search' %}?{{pd}}&page={{ asset_assignments.next_page_number }}"
class="oh-pagination__link"
>{% trans "Next" %}</a
>
</li>
<li class="oh-pagination__item oh-pagination__item--wide">
<a
hx-target="#historyTable"
hx-get="{% url 'asset-history-search' %}?{{pd}}&page={{ asset_assignments.paginator.num_pages }}"
class="oh-pagination__link"
>{% trans "Last" %}</a
>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
<div
class="oh-modal"
id="historySingleModal"
role="dialog"
aria-labelledby="historySingleModal"
aria-hidden="true"
>
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<h2 class="oh-modal__dialog-title" id="historySingleModalLabel">
{% trans "Asset Details" %}
</h2>
<button class="oh-modal__close" aria-label="Close">
<ion-icon name="close-outline"></ion-icon>
</button>
<div
class="oh-modal__dialog-body p-0 pt-2"
id="historySingleModalBody"
></div>
</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
{% load i18n %} {% load i18n %}
<h5 >{% trans "Asset Allocation" %}</h5> <h5 >{% trans "Asset Allocation" %}</h5>
<form hx-post="{%url 'asset-allocate-creation' %}" hx-target="#asset-request-allocation-modal-target"> <form hx-post="{%url 'asset-allocate-creation' %}" hx-target="#asset-request-allocation-modal-target" hx-encoding="multipart/form-data" >
{% csrf_token %} {% csrf_token %}
<div class=" m-3"> <div class=" m-3">
<div class="oh-input__group "> <div class="oh-input__group ">
@@ -18,6 +18,11 @@
{{asset_allocation_form.assigned_by_employee_id}} {{asset_allocation_form.assigned_by_employee_id}}
{{asset_allocation_form.assigned_by_employee_id.errors}} {{asset_allocation_form.assigned_by_employee_id.errors}}
</div> </div>
<div class="oh-input__group ">
<label class="oh-input__label" for="{{asset_allocation_form.assign_images.id_for_label}}">{% trans "Assign condition images" %}</label>
{{asset_allocation_form.assign_images}}
{{asset_allocation_form.assign_images.errors}}
</div>
</div> </div>
<div class="oh-modal__dialog-footer"> <div class="oh-modal__dialog-footer">
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow"> <button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">

View File

@@ -152,4 +152,19 @@ urlpatterns = [
path( path(
"asset-category-chart/", views.asset_category_chart, name="asset-category-chart" "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",
),
] ]

View File

@@ -2,19 +2,22 @@
asset.py asset.py
This module is used to """ This module is used to """
from datetime import date, datetime from datetime import date, datetime
import json import json
from django.db.models import Q from django.db.models import Q
from urllib.parse import parse_qs from urllib.parse import parse_qs
import pandas as pd import pandas as pd
from django.db.models import ProtectedError from django.db.models import ProtectedError
from django.shortcuts import render, redirect from django.shortcuts import get_object_or_404, render, redirect
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.contrib import messages from django.contrib import messages
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from base.methods import closest_numbers, get_key_instances, get_pagination from attendance.methods.group_by import group_by_queryset
from base.methods import closest_numbers, get_key_instances, get_pagination, sortby
from base.models import Company from base.models import Company
from base.views import paginator_qry
from employee.models import EmployeeWorkInformation from employee.models import EmployeeWorkInformation
from notifications.signals import notify from notifications.signals import notify
from horilla.decorators import login_required, hx_request_required, manager_can_enter from horilla.decorators import login_required, hx_request_required, manager_can_enter
@@ -26,6 +29,7 @@ from asset.models import (
AssetAssignment, AssetAssignment,
AssetCategory, AssetCategory,
AssetLot, AssetLot,
ReturnImages,
) )
from asset.forms import ( from asset.forms import (
AssetBatchForm, AssetBatchForm,
@@ -40,6 +44,8 @@ from asset.forms import (
from asset.filters import ( from asset.filters import (
AssetAllocationFilter, AssetAllocationFilter,
AssetAllocationReGroup, AssetAllocationReGroup,
AssetHistoryFilter,
AssetHistoryReGroup,
AssetRequestFilter, AssetRequestFilter,
AssetRequestReGroup, AssetRequestReGroup,
CustomAssetFilter, CustomAssetFilter,
@@ -614,7 +620,9 @@ def asset_allocate_creation(request):
- to allocated view. - to allocated view.
""" """
form = AssetAllocationForm() form = AssetAllocationForm(
initial={"assigned_by_employee_id": request.user.employee_get}
)
context = {"asset_allocation_form": form} context = {"asset_allocation_form": form}
if request.method == "POST": if request.method == "POST":
form = AssetAllocationForm(request.POST) form = AssetAllocationForm(request.POST)
@@ -623,20 +631,31 @@ def asset_allocate_creation(request):
asset = Asset.objects.filter(id=asset).first() asset = Asset.objects.filter(id=asset).first()
asset.asset_status = "In use" asset.asset_status = "In use"
asset.save() asset.save()
form.save() instance = form.save()
files = request.FILES.getlist("assign_images")
attachments = []
if request.FILES:
for file in files:
attachment = ReturnImages()
attachment.image = file
attachment.save()
attachments.append(attachment)
instance.assign_images.add(*attachments)
messages.success(request, _("Asset allocated successfully!.")) messages.success(request, _("Asset allocated successfully!."))
return HttpResponse("<script>window.location.reload()</script>") return HttpResponse("<script>window.location.reload()</script>")
context["asset_allocation_form"] = form context["asset_allocation_form"] = form
return render(request, "request_allocation/asset_allocation_creation.html", context) return render(request, "request_allocation/asset_allocation_creation.html", context)
def asset_allocate_return_request(request, asset_id): def asset_allocate_return_request(request, asset_id):
asset_assign = AssetAssignment.objects.get(id=asset_id) asset_assign = AssetAssignment.objects.get(id=asset_id)
asset_assign.return_request = True asset_assign.return_request = True
asset_assign.save() asset_assign.save()
message = _("Return request for {} initiated.").format(asset_assign.asset_id) message = _("Return request for {} initiated.").format(asset_assign.asset_id)
messages.info(request, message) messages.info(request, message)
return redirect('asset-request-allocation-view') return redirect("asset-request-allocation-view")
@login_required @login_required
def asset_allocate_return(request, asset_id): def asset_allocate_return(request, asset_id):
@@ -660,6 +679,8 @@ def asset_allocate_return(request, asset_id):
asset_return_status = request.POST.get("return_status") asset_return_status = request.POST.get("return_status")
asset_return_date = request.POST.get("return_date") asset_return_date = request.POST.get("return_date")
asset_return_condition = request.POST.get("return_condition") asset_return_condition = request.POST.get("return_condition")
files = request.FILES.getlist("return_images")
attachments = []
context = {"asset_return_form": asset_return_form, "asset_id": asset_id} context = {"asset_return_form": asset_return_form, "asset_id": asset_id}
response = render(request, "asset/asset_return_form.html", context) response = render(request, "asset/asset_return_form.html", context)
if asset_return_status == "Healthy": if asset_return_status == "Healthy":
@@ -671,6 +692,13 @@ def asset_allocate_return(request, asset_id):
asset_allocation.return_condition = asset_return_condition asset_allocation.return_condition = asset_return_condition
asset_allocation.return_request = False asset_allocation.return_request = False
asset_allocation.save() asset_allocation.save()
if request.FILES:
for file in files:
attachment = ReturnImages()
attachment.image = file
attachment.save()
attachments.append(attachment)
asset_allocation.return_images.add(*attachments)
asset.asset_status = "Available" asset.asset_status = "Available"
asset.save() asset.save()
messages.info(request, _("Asset Return Successful !.")) messages.info(request, _("Asset Return Successful !."))
@@ -687,6 +715,13 @@ def asset_allocate_return(request, asset_id):
asset_allocation.return_status = asset_return_status asset_allocation.return_status = asset_return_status
asset_allocation.return_condition = asset_return_condition asset_allocation.return_condition = asset_return_condition
asset_allocation.save() asset_allocation.save()
if request.FILES:
for file in files:
attachment = ReturnImages()
attachment.image = file
attachment.save()
attachments.append(attachment)
asset_allocation.return_images.add(*attachments)
messages.info(request, _("Asset Return Successful!.")) messages.info(request, _("Asset Return Successful!."))
return HttpResponse( return HttpResponse(
response.content.decode("utf-8") + "<script>location.reload();</script>" response.content.decode("utf-8") + "<script>location.reload();</script>"
@@ -910,7 +945,10 @@ def asset_request_alloaction_view_search_filter(request):
def asset_request_individual_view(request, id): def asset_request_individual_view(request, id):
asset_request = AssetRequest.objects.get(id=id) asset_request = AssetRequest.objects.get(id=id)
context = {"asset_request": asset_request,'dashboard': request.GET.get('dashboard'),} context = {
"asset_request": asset_request,
"dashboard": request.GET.get("dashboard"),
}
requests_ids_json = request.GET.get("requests_ids") requests_ids_json = request.GET.get("requests_ids")
if requests_ids_json: if requests_ids_json:
requests_ids = json.loads(requests_ids_json) requests_ids = json.loads(requests_ids_json)
@@ -1352,7 +1390,7 @@ def asset_available_chart(request):
"labels": labels, "labels": labels,
"dataset": dataset, "dataset": dataset,
"message": _("Oops!! No Asset found..."), "message": _("Oops!! No Asset found..."),
"emptyImageSrc":"/static/images/ui/asset.png", "emptyImageSrc": "/static/images/ui/asset.png",
} }
return JsonResponse(response) return JsonResponse(response)
@@ -1382,6 +1420,58 @@ def asset_category_chart(request):
"labels": labels, "labels": labels,
"dataset": dataset, "dataset": dataset,
"message": _("Oops!! No Asset found..."), "message": _("Oops!! No Asset found..."),
"emptyImageSrc":"/static/images/ui/asset.png", "emptyImageSrc": "/static/images/ui/asset.png",
} }
return JsonResponse(response) return JsonResponse(response)
@login_required
def asset_history(request):
previous_data = request.GET.urlencode() + "&returned_assets=True"
asset_assignments = AssetHistoryFilter({"returned_assets": "True"}).qs.order_by("-id")
data_dict = parse_qs(previous_data)
get_key_instances(AssetAssignment, data_dict)
asset_assignments= paginator_qry(asset_assignments,request.GET.get("page"))
context = {
"asset_assignments": asset_assignments,
"f": AssetHistoryFilter(),
"filter_dict": data_dict,
"gp_fields": AssetHistoryReGroup().fields,
"pd":previous_data,
}
return render(request, "asset_history/asset_history_view.html", context)
@login_required
def asset_history_single_view(request, asset_id):
asset_assignment = get_object_or_404(AssetAssignment, id=asset_id)
return render(
request,
"asset_history/asset_history_single_view.html",
{"asset_assignment": asset_assignment},
)
@login_required
def asset_history_search(request):
previous_data = request.GET.urlencode()
asset_assignments = AssetHistoryFilter(request.GET).qs.order_by("-id")
asset_assignments = sortby(request, asset_assignments, "sortby")
template = "asset_history/asset_history_list.html"
field = request.GET.get("field")
if field != "" and field is not None:
asset_assignments = group_by_queryset(asset_assignments, field, request.GET.get("page"), "page")
template = "asset_history/group_by.html"
asset_assignments= paginator_qry(asset_assignments,request.GET.get("page"))
data_dict = parse_qs(previous_data)
get_key_instances(AssetAssignment, data_dict)
return render(
request,
template,
{
"asset_assignments": asset_assignments,
"filter_dict": data_dict,
"field": field,
"pd":previous_data,
},
)

View File

@@ -857,6 +857,15 @@
>{% trans "Request and Allocation" %}</a >{% trans "Request and Allocation" %}</a
> >
</li> </li>
{% if perms.asset.view_assetcategory %}
<li class="oh-sidebar__submenu-item">
<a
href="{% url 'asset-history' %}"
class="oh-sidebar__submenu-link"
>{% trans "Asset History" %}</a
>
</li>
{% endif %}
</ul> </ul>
</div> </div>
</li> </li>