[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"),
("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 django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from employee.forms import MultipleFileField
from employee.models import Employee
from asset.models import Asset, AssetDocuments, AssetReport, AssetRequest, AssetAssignment, AssetCategory, AssetLot
from base.methods import reload_queryset
@@ -256,6 +257,9 @@ class AssetAllocationForm(ModelForm):
asset_status="Available"
)
self.fields["assign_images"] = MultipleFileField()
self.fields["assign_images"].required = True
class Meta:
"""
Specifies the model and fields to be used for the AssetAllocationForm.
@@ -269,7 +273,7 @@ class AssetAllocationForm(ModelForm):
model = AssetAssignment
fields = "__all__"
exclude = ["return_date", "return_condition", "assigned_date"]
exclude = ["return_date", "return_condition", "assigned_date", "return_images"]
widgets = {
"asset_id": forms.Select(attrs={"class": "oh-select oh-select-2 "}),
"assigned_to_employee_id": forms.Select(
@@ -300,7 +304,7 @@ class AssetReturnForm(ModelForm):
"""
model = AssetAssignment
fields = ["return_date", "return_condition", "return_status"]
fields = ["return_date", "return_condition", "return_status", "return_images"]
widgets = {
"return_date": forms.DateInput(
attrs={"type": "date", "class": "oh-input w-100", "required": "true"}
@@ -324,6 +328,8 @@ class AssetReturnForm(ModelForm):
super(AssetReturnForm, self).__init__(*args, **kwargs)
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):
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}'
class ReturnImages(models.Model):
image = models.FileField(upload_to="asset/return_images/", blank=True, null=True)
class AssetAssignment(models.Model):
"""
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)
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):

View File

@@ -1,6 +1,6 @@
{% load i18n %}
<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 %}
<div class="m-3">
<div class="oh-input__group ">
@@ -16,6 +16,10 @@
<label class="oh-input__label" for="objective">{% trans "Return Condition" %}</label>
{{asset_return_form.return_condition}}
</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 class="oh-modal__dialog-footer justify-content-between mt-3">
<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 %}
<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 %}
<div class=" m-3">
<div class="oh-input__group ">
@@ -18,6 +18,11 @@
{{asset_allocation_form.assigned_by_employee_id}}
{{asset_allocation_form.assigned_by_employee_id.errors}}
</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 class="oh-modal__dialog-footer">
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">

View File

@@ -152,4 +152,19 @@ urlpatterns = [
path(
"asset-category-chart/", views.asset_category_chart, name="asset-category-chart"
),
path(
"asset-history",
views.asset_history,
name="asset-history",
),
path(
"asset-history-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
This module is used to """
from datetime import date, datetime
import json
from django.db.models import Q
from urllib.parse import parse_qs
import pandas as pd
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.contrib import messages
from django.core.paginator import Paginator
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.views import paginator_qry
from employee.models import EmployeeWorkInformation
from notifications.signals import notify
from horilla.decorators import login_required, hx_request_required, manager_can_enter
@@ -26,6 +29,7 @@ from asset.models import (
AssetAssignment,
AssetCategory,
AssetLot,
ReturnImages,
)
from asset.forms import (
AssetBatchForm,
@@ -40,6 +44,8 @@ from asset.forms import (
from asset.filters import (
AssetAllocationFilter,
AssetAllocationReGroup,
AssetHistoryFilter,
AssetHistoryReGroup,
AssetRequestFilter,
AssetRequestReGroup,
CustomAssetFilter,
@@ -614,7 +620,9 @@ def asset_allocate_creation(request):
- to allocated view.
"""
form = AssetAllocationForm()
form = AssetAllocationForm(
initial={"assigned_by_employee_id": request.user.employee_get}
)
context = {"asset_allocation_form": form}
if request.method == "POST":
form = AssetAllocationForm(request.POST)
@@ -623,20 +631,31 @@ def asset_allocate_creation(request):
asset = Asset.objects.filter(id=asset).first()
asset.asset_status = "In use"
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!."))
return HttpResponse("<script>window.location.reload()</script>")
context["asset_allocation_form"] = form
return render(request, "request_allocation/asset_allocation_creation.html", context)
def asset_allocate_return_request(request, asset_id):
asset_assign = AssetAssignment.objects.get(id=asset_id)
asset_assign.return_request = True
asset_assign.save()
message = _("Return request for {} initiated.").format(asset_assign.asset_id)
messages.info(request, message)
return redirect('asset-request-allocation-view')
return redirect("asset-request-allocation-view")
@login_required
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_date = request.POST.get("return_date")
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}
response = render(request, "asset/asset_return_form.html", context)
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_request = False
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.save()
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_condition = asset_return_condition
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!."))
return HttpResponse(
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):
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")
if requests_ids_json:
requests_ids = json.loads(requests_ids_json)
@@ -1352,7 +1390,7 @@ def asset_available_chart(request):
"labels": labels,
"dataset": dataset,
"message": _("Oops!! No Asset found..."),
"emptyImageSrc":"/static/images/ui/asset.png",
"emptyImageSrc": "/static/images/ui/asset.png",
}
return JsonResponse(response)
@@ -1382,6 +1420,58 @@ def asset_category_chart(request):
"labels": labels,
"dataset": dataset,
"message": _("Oops!! No Asset found..."),
"emptyImageSrc":"/static/images/ui/asset.png",
"emptyImageSrc": "/static/images/ui/asset.png",
}
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
>
</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>
</div>
</li>