update
This commit is contained in:
parent
b9b8c69129
commit
1f0a6bff5f
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
Binary file not shown.
@ -38,6 +38,7 @@ DJANGO_APPS = [
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.humanize',
|
||||
]
|
||||
|
||||
THIRD_PARTY_APPS = [
|
||||
|
||||
Binary file not shown.
@ -341,26 +341,26 @@ class InventoryLocationDetailView(LoginRequiredMixin, DetailView):
|
||||
|
||||
# Stock items at this location
|
||||
context['stock_items'] = InventoryStock.objects.filter(
|
||||
tenant=self.request.user.tenant,
|
||||
inventory_item__tenant=self.request.user.tenant,
|
||||
location=location
|
||||
).select_related('item').order_by('item__item_name')
|
||||
).select_related('inventory_item').order_by('inventory_item__item_name')
|
||||
|
||||
# Location statistics
|
||||
context['total_items'] = InventoryStock.objects.filter(
|
||||
tenant=self.request.user.tenant,
|
||||
inventory_item__tenant=self.request.user.tenant,
|
||||
location=location
|
||||
).count()
|
||||
|
||||
context['total_quantity'] = InventoryStock.objects.filter(
|
||||
tenant=self.request.user.tenant,
|
||||
inventory_item__tenant=self.request.user.tenant,
|
||||
location=location
|
||||
).aggregate(total=Sum('quantity'))['total'] or 0
|
||||
).aggregate(total=Sum('quantity_available'))['total'] or 0
|
||||
|
||||
context['total_value'] = InventoryStock.objects.filter(
|
||||
tenant=self.request.user.tenant,
|
||||
inventory_item__tenant=self.request.user.tenant,
|
||||
location=location
|
||||
).aggregate(
|
||||
total_value=Sum(F('quantity') * F('unit_cost'))
|
||||
total_value=Sum(F('quantity_available') * F('unit_cost'))
|
||||
)['total_value'] or 0
|
||||
|
||||
return context
|
||||
@ -503,27 +503,27 @@ class InventoryItemDetailView(LoginRequiredMixin, DetailView):
|
||||
|
||||
# Stock information across all locations
|
||||
context['stock_locations'] = InventoryStock.objects.filter(
|
||||
tenant=self.request.user.tenant,
|
||||
item=item
|
||||
inventory_item__tenant=self.request.user.tenant,
|
||||
inventory_item=item
|
||||
).select_related('location').order_by('location__location_name')
|
||||
|
||||
# Item statistics
|
||||
context['total_stock'] = InventoryStock.objects.filter(
|
||||
tenant=self.request.user.tenant,
|
||||
item=item
|
||||
).aggregate(total=Sum('quantity'))['total'] or 0
|
||||
inventory_item__tenant=self.request.user.tenant,
|
||||
inventory_item=item
|
||||
).aggregate(total=Sum('quantity_available'))['total'] or 0
|
||||
|
||||
context['total_value'] = InventoryStock.objects.filter(
|
||||
tenant=self.request.user.tenant,
|
||||
item=item
|
||||
inventory_item__tenant=self.request.user.tenant,
|
||||
inventory_item=item
|
||||
).aggregate(
|
||||
total_value=Sum(F('quantity') * F('unit_cost'))
|
||||
total_value=Sum(F('quantity_available') * F('unit_cost'))
|
||||
)['total_value'] or 0
|
||||
|
||||
# Recent purchase orders for this item
|
||||
context['recent_orders'] = PurchaseOrderItem.objects.filter(
|
||||
purchase_order__tenant=self.request.user.tenant,
|
||||
item=item
|
||||
inventory_item=item
|
||||
).select_related('purchase_order').order_by('-purchase_order__order_date')[:5]
|
||||
|
||||
return context
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -4,9 +4,9 @@
|
||||
{% block title %}{{ object.item_name }} - Inventory Item Details{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'assets/plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'assets/plugins/chart.js/dist/Chart.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'plugins/chart.js/dist/Chart.min.css' %}" rel="stylesheet" />
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@ -96,11 +96,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">Unit Cost:</td>
|
||||
<td class="fw-bold text-success">${{ object.unit_cost }}</td>
|
||||
<td class="fw-bold text-success"><span class="symbol">ê</span>{{ object.unit_cost|floatformat:'2g' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">List Price:</td>
|
||||
<td>${{ object.list_price }}</td>
|
||||
<td><span class="symbol">ê</span>{{ object.list_price|floatformat:'2g' }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@ -237,7 +237,7 @@
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-center p-3 bg-success text-white rounded">
|
||||
<div class="fs-24px fw-bold">${{ object.total_value|floatformat:2 }}</div>
|
||||
<div class="fs-24px fw-bold"><span class="symbol">ê</span>{{ object.total_value|floatformat:'2g' }}</div>
|
||||
<div class="small">Total Value</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -303,10 +303,15 @@
|
||||
<span class="text-muted">No expiration</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span class="badge bg-{{ stock.get_quality_status_color }}">
|
||||
{{ stock.get_quality_status_display }}
|
||||
</span>
|
||||
{% if stock.quality_status == 'GOOD' %}
|
||||
<span class="badge bg-success">{{ stock.get_quality_status_display }}</span>
|
||||
{% elif stock.quality_status == 'QUARANTINE' %}
|
||||
<span class="badge bg-warning">{{ stock.get_quality_status_display }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-danger">{{ stock.get_quality_status_display }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'inventory:stock_detail' stock.pk %}" class="btn btn-xs btn-outline-primary">
|
||||
@ -336,7 +341,7 @@
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Recent Transactions</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="{% url 'inventory:transaction_list' %}?item={{ object.pk }}" class="btn btn-xs btn-outline-secondary me-2">
|
||||
<a href="#" class="btn btn-xs btn-outline-secondary me-2">
|
||||
<i class="fa fa-list"></i> View All
|
||||
</a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
@ -405,7 +410,7 @@
|
||||
<i class="fa fa-plus me-2"></i>Add Stock
|
||||
</a>
|
||||
|
||||
<a href="{% url 'inventory:stock_adjustment_create' %}?item={{ object.pk }}" class="btn btn-warning">
|
||||
<a href="#" class="btn btn-warning">
|
||||
<i class="fa fa-adjust me-2"></i>Adjust Stock
|
||||
</a>
|
||||
|
||||
@ -594,32 +599,32 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'assets/plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
|
||||
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
|
||||
<script src="{% static 'assets/plugins/chart.js/dist/Chart.min.js' %}"></script>
|
||||
<script src="{% static 'plugins/datatables.net/js/dataTables.min.js' %}"></script>
|
||||
<script src="{% static 'plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
|
||||
<script src="{% static 'plugins/chart.js/dist/chart.js' %}"></script>
|
||||
<script>
|
||||
var stockChart;
|
||||
|
||||
function printItemLabel() {
|
||||
window.open('{% url "inventory:item_label_print" object.pk %}', '_blank');
|
||||
}
|
||||
{#function printItemLabel() {#}
|
||||
{# window.open('{% url "inventory:item_label_print" object.pk %}', '_blank');#}
|
||||
{# }#}
|
||||
|
||||
function showStockChart() {
|
||||
$('#stockChartModal').modal('show');
|
||||
loadStockChart();
|
||||
}
|
||||
|
||||
function loadStockChart() {
|
||||
$.ajax({
|
||||
url: '{% url "inventory:item_stock_history" object.pk %}',
|
||||
success: function(data) {
|
||||
renderStockChart(data);
|
||||
},
|
||||
error: function() {
|
||||
toastr.error('Failed to load stock history data');
|
||||
}
|
||||
});
|
||||
}
|
||||
{#function loadStockChart() {#}
|
||||
{# $.ajax({#}
|
||||
{# url: '{% url "inventory:item_stock_history" object.pk %}',#}
|
||||
{# success: function(data) {#}
|
||||
{# renderStockChart(data);#}
|
||||
{# },#}
|
||||
{# error: function() {#}
|
||||
{# toastr.error('Failed to load stock history data');#}
|
||||
{# }#}
|
||||
{# });#}
|
||||
{# }#}
|
||||
|
||||
function renderStockChart(data) {
|
||||
var ctx = document.getElementById('stock-chart').getContext('2d');
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
{% block title %}{% if object %}Edit {{ object.item_name }}{% else %}Add New Item{% endif %} - Inventory{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<link href="{% static 'assets/plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'assets/plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'assets/plugins/jquery-file-upload/css/jquery.fileupload.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' %}" rel="stylesheet" />
|
||||
{#<link href="{% static 'plugins/jquery-file-upload/css/jquery.fileupload.css' %}" rel="stylesheet" />#}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@ -642,30 +642,30 @@ function toggleConditionalFields() {
|
||||
}
|
||||
}
|
||||
|
||||
function generateItemCode() {
|
||||
var category = $('#id_category').val();
|
||||
var itemType = $('#id_item_type').val();
|
||||
|
||||
if (!category) {
|
||||
toastr.warning('Please select a category first');
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '{% url "inventory:generate_item_code" %}',
|
||||
data: {
|
||||
'category': category,
|
||||
'item_type': itemType
|
||||
},
|
||||
success: function(response) {
|
||||
$('#id_item_code').val(response.item_code);
|
||||
toastr.success('Item code generated: ' + response.item_code);
|
||||
},
|
||||
error: function() {
|
||||
toastr.error('Failed to generate item code');
|
||||
}
|
||||
});
|
||||
}
|
||||
{#function generateItemCode() {#}
|
||||
{# var category = $('#id_category').val();#}
|
||||
{# var itemType = $('#id_item_type').val();#}
|
||||
{# #}
|
||||
{# if (!category) {#}
|
||||
{# toastr.warning('Please select a category first');#}
|
||||
{# return;#}
|
||||
{# }#}
|
||||
{# #}
|
||||
{# $.ajax({#}
|
||||
{# url: '{% url "inventory:generate_item_code" %}',#}
|
||||
{# data: {#}
|
||||
{# 'category': category,#}
|
||||
{# 'item_type': itemType#}
|
||||
{# },#}
|
||||
{# success: function(response) {#}
|
||||
{# $('#id_item_code').val(response.item_code);#}
|
||||
{# toastr.success('Item code generated: ' + response.item_code);#}
|
||||
{# },#}
|
||||
{# error: function() {#}
|
||||
{# toastr.error('Failed to generate item code');#}
|
||||
{# }#}
|
||||
{# });#}
|
||||
{# }#}
|
||||
|
||||
function scanBarcode() {
|
||||
// Implementation for barcode scanning
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load allauth %}
|
||||
{% load static humanize %}
|
||||
|
||||
{% block title %}Inventory Items - Inventory Management{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<link href="{% static 'plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' %}" rel="stylesheet" />
|
||||
{% endblock %}
|
||||
@ -39,71 +38,81 @@
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<!-- Filters and Search -->
|
||||
<div class="row mb-3">
|
||||
<!-- Filters and Search (GET form) -->
|
||||
<form id="filter-form" method="get" class="row g-3 mb-3 align-items-end">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Search Items</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="search-input" placeholder="Item name, code, or description...">
|
||||
<button class="btn btn-outline-secondary" type="button" id="search-btn">
|
||||
<input type="text" class="form-control" name="q" id="search-input" value="{{ request.GET.q|default_if_none:'' }}" placeholder="Item name, code, or description...">
|
||||
<button class="btn btn-outline-secondary" type="submit" id="search-btn">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Category</label>
|
||||
<select class="form-select" id="category-filter">
|
||||
<option value="">All Categories</option>
|
||||
<option value="MEDICAL_SUPPLIES">Medical Supplies</option>
|
||||
<option value="PHARMACEUTICALS">Pharmaceuticals</option>
|
||||
<option value="SURGICAL_INSTRUMENTS">Surgical Instruments</option>
|
||||
<option value="DIAGNOSTIC_EQUIPMENT">Diagnostic Equipment</option>
|
||||
<option value="PATIENT_CARE">Patient Care Equipment</option>
|
||||
<option value="LABORATORY_SUPPLIES">Laboratory Supplies</option>
|
||||
<option value="RADIOLOGY_SUPPLIES">Radiology Supplies</option>
|
||||
<option value="OTHER">Other</option>
|
||||
<select class="form-select" name="category" id="category-filter">
|
||||
<option value="" {% if not request.GET.category %}selected{% endif %}>All Categories</option>
|
||||
<option value="MEDICAL_SUPPLIES" {% if request.GET.category == 'MEDICAL_SUPPLIES' %}selected{% endif %}>Medical Supplies</option>
|
||||
<option value="PHARMACEUTICALS" {% if request.GET.category == 'PHARMACEUTICALS' %}selected{% endif %}>Pharmaceuticals</option>
|
||||
<option value="SURGICAL_INSTRUMENTS" {% if request.GET.category == 'SURGICAL_INSTRUMENTS' %}selected{% endif %}>Surgical Instruments</option>
|
||||
<option value="DIAGNOSTIC_EQUIPMENT" {% if request.GET.category == 'DIAGNOSTIC_EQUIPMENT' %}selected{% endif %}>Diagnostic Equipment</option>
|
||||
<option value="PATIENT_CARE" {% if request.GET.category == 'PATIENT_CARE' %}selected{% endif %}>Patient Care Equipment</option>
|
||||
<option value="LABORATORY_SUPPLIES" {% if request.GET.category == 'LABORATORY_SUPPLIES' %}selected{% endif %}>Laboratory Supplies</option>
|
||||
<option value="RADIOLOGY_SUPPLIES" {% if request.GET.category == 'RADIOLOGY_SUPPLIES' %}selected{% endif %}>Radiology Supplies</option>
|
||||
<option value="OTHER" {% if request.GET.category == 'OTHER' %}selected{% endif %}>Other</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Item Type</label>
|
||||
<select class="form-select" id="type-filter">
|
||||
<option value="">All Types</option>
|
||||
<option value="CONSUMABLE">Consumable</option>
|
||||
<option value="REUSABLE">Reusable</option>
|
||||
<option value="EQUIPMENT">Equipment</option>
|
||||
<option value="MEDICATION">Medication</option>
|
||||
<option value="DEVICE">Medical Device</option>
|
||||
<select class="form-select" name="item_type" id="type-filter">
|
||||
<option value="" {% if not request.GET.item_type %}selected{% endif %}>All Types</option>
|
||||
<option value="CONSUMABLE" {% if request.GET.item_type == 'CONSUMABLE' %}selected{% endif %}>Consumable</option>
|
||||
<option value="REUSABLE" {% if request.GET.item_type == 'REUSABLE' %}selected{% endif %}>Reusable</option>
|
||||
<option value="EQUIPMENT" {% if request.GET.item_type == 'EQUIPMENT' %}selected{% endif %}>Equipment</option>
|
||||
<option value="MEDICATION" {% if request.GET.item_type == 'MEDICATION' %}selected{% endif %}>Medication</option>
|
||||
<option value="DEVICE" {% if request.GET.item_type == 'DEVICE' %}selected{% endif %}>Medical Device</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Stock Status</label>
|
||||
<select class="form-select" id="stock-filter">
|
||||
<option value="">All Items</option>
|
||||
<option value="in_stock">In Stock</option>
|
||||
<option value="low_stock">Low Stock</option>
|
||||
<option value="out_of_stock">Out of Stock</option>
|
||||
<option value="needs_reorder">Needs Reorder</option>
|
||||
<select class="form-select" name="stock_status" id="stock-filter">
|
||||
<option value="" {% if not request.GET.stock_status %}selected{% endif %}>All Items</option>
|
||||
<option value="in_stock" {% if request.GET.stock_status == 'in_stock' %}selected{% endif %}>In Stock</option>
|
||||
<option value="low_stock" {% if request.GET.stock_status == 'low_stock' %}selected{% endif %}>Low Stock</option>
|
||||
<option value="out_of_stock" {% if request.GET.stock_status == 'out_of_stock' %}selected{% endif %}>Out of Stock</option>
|
||||
<option value="needs_reorder" {% if request.GET.stock_status == 'needs_reorder' %}selected{% endif %}>Needs Reorder</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Manufacturer</label>
|
||||
<select class="form-select select2" id="manufacturer-filter">
|
||||
<select class="form-select select2" name="manufacturer" id="manufacturer-filter" data-current="{{ request.GET.manufacturer|default_if_none:'' }}">
|
||||
<option value="">All Manufacturers</option>
|
||||
<!-- Options will be loaded dynamically -->
|
||||
{% if manufacturers %}
|
||||
{% for m in manufacturers %}
|
||||
<option value="{{ m.id }}" {% if request.GET.manufacturer == m.id|stringformat:'s' %}selected{% endif %}>
|
||||
{{ m.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Quick Stats -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-lg-3 col-md-6 mb-3">
|
||||
<div class="card border-0 bg-primary text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="flex-fill">
|
||||
<div class="fs-10px text-white-transparent-5 mb-1">TOTAL ITEMS</div>
|
||||
<div class="fs-18px fw-900 text-white" id="total-items">-</div>
|
||||
<div class="fs-18px fw-900 text-white" id="total-items">{{ stats.total_items|default:"-" }}</div>
|
||||
</div>
|
||||
<div class="w-50px h-50px bg-white-transparent-2 rounded-circle d-flex align-items-center justify-content-center">
|
||||
<i class="fa fa-boxes fa-lg text-white"></i>
|
||||
@ -111,14 +120,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 mb-3">
|
||||
<div class="card border-0 bg-warning text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="flex-fill">
|
||||
<div class="fs-10px text-white-transparent-5 mb-1">LOW STOCK ITEMS</div>
|
||||
<div class="fs-18px fw-900 text-white" id="low-stock-count">-</div>
|
||||
<div class="fs-18px fw-900 text-white" id="low-stock-count">{{ stats.low_stock_count|default:"-" }}</div>
|
||||
</div>
|
||||
<div class="w-50px h-50px bg-white-transparent-2 rounded-circle d-flex align-items-center justify-content-center">
|
||||
<i class="fa fa-exclamation-triangle fa-lg text-white"></i>
|
||||
@ -126,14 +133,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 mb-3">
|
||||
<div class="card border-0 bg-danger text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="flex-fill">
|
||||
<div class="fs-10px text-white-transparent-5 mb-1">OUT OF STOCK</div>
|
||||
<div class="fs-18px fw-900 text-white" id="out-of-stock-count">-</div>
|
||||
<div class="fs-18px fw-900 text-white" id="out-of-stock-count">{{ stats.out_of_stock_count|default:"-" }}</div>
|
||||
</div>
|
||||
<div class="w-50px h-50px bg-white-transparent-2 rounded-circle d-flex align-items-center justify-content-center">
|
||||
<i class="fa fa-times-circle fa-lg text-white"></i>
|
||||
@ -141,14 +146,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 mb-3">
|
||||
<div class="card border-0 bg-success text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="flex-fill">
|
||||
<div class="fs-10px text-white-transparent-5 mb-1">TOTAL VALUE</div>
|
||||
<div class="fs-18px fw-900 text-white" id="total-value">-</div>
|
||||
<div class="fs-18px fw-900 text-white" id="total-value">
|
||||
{% if stats.total_value %}${{ stats.total_value|floatformat:2|intcomma }}{% else %}-{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-50px h-50px bg-white-transparent-2 rounded-circle d-flex align-items-center justify-content-center">
|
||||
<i class="fa fa-dollar-sign fa-lg text-white"></i>
|
||||
@ -157,7 +162,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
@ -170,49 +174,49 @@
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#" onclick="bulkUpdateCategory()">
|
||||
<li><a class="dropdown-item" href="#" id="bulk-update-category">
|
||||
<i class="fa fa-tag me-2"></i>Update Category
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="bulkUpdateSupplier()">
|
||||
<li><a class="dropdown-item" href="#" id="bulk-update-supplier">
|
||||
<i class="fa fa-truck me-2"></i>Update Supplier
|
||||
</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="bulkExport()">
|
||||
<i class="fa fa-download me-2"></i>Export Selected
|
||||
</a></li>
|
||||
<li><a class="dropdown-item text-danger" href="#" onclick="bulkDeactivate()">
|
||||
{# <li><a class="dropdown-item" href="{% url 'inventory:item_export' %}?{{ request.GET.urlencode }}">#}
|
||||
{# <i class="fa fa-download me-2"></i>Export Current View#}
|
||||
{# </a></li>#}
|
||||
<li><a class="dropdown-item text-danger" href="#" id="bulk-deactivate">
|
||||
<i class="fa fa-ban me-2"></i>Deactivate Selected
|
||||
</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="refreshTable()">
|
||||
<a class="btn btn-outline-secondary" href="?{{ request.GET.urlencode }}">
|
||||
<i class="fa fa-refresh me-2"></i>Refresh
|
||||
</button>
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="showStockAlerts()">
|
||||
<i class="fa fa-bell me-2"></i>Stock Alerts
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
||||
<i class="fa fa-download me-2"></i>Export
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#" onclick="exportData('csv')">
|
||||
<i class="fa fa-file-csv me-2"></i>Export as CSV
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportData('excel')">
|
||||
<i class="fa fa-file-excel me-2"></i>Export as Excel
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportData('pdf')">
|
||||
<i class="fa fa-file-pdf me-2"></i>Export as PDF
|
||||
</a></li>
|
||||
</ul>
|
||||
{# <ul class="dropdown-menu">#}
|
||||
{# <li><a class="dropdown-item" href="{% url 'inventory:item_export' %}?format=csv&{{ request.GET.urlencode }}">#}
|
||||
{# <i class="fa fa-file-csv me-2"></i>Export as CSV#}
|
||||
{# </a></li>#}
|
||||
{# <li><a class="dropdown-item" href="{% url 'inventory:item_export' %}?format=excel&{{ request.GET.urlencode }}">#}
|
||||
{# <i class="fa fa-file-excel me-2"></i>Export as Excel#}
|
||||
{# </a></li>#}
|
||||
{# <li><a class="dropdown-item" href="{% url 'inventory:item_export' %}?format=pdf&{{ request.GET.urlencode }}">#}
|
||||
{# <i class="fa fa-file-pdf me-2"></i>Export as PDF#}
|
||||
{# </a></li>#}
|
||||
{# </ul>#}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Items Table -->
|
||||
<!-- Items Table (regular table) -->
|
||||
<div class="table-responsive">
|
||||
<table id="items-table" class="table table-striped table-bordered align-middle">
|
||||
<table class="table table-striped table-bordered align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="30">
|
||||
@ -229,13 +233,98 @@
|
||||
<th>Unit Cost</th>
|
||||
<th>Total Value</th>
|
||||
<th>Status</th>
|
||||
<th width="120">Actions</th>
|
||||
<th width="140">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Data will be loaded via AJAX -->
|
||||
{% if items %}
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input row-checkbox" type="checkbox" value="{{ item.id }}">
|
||||
</div>
|
||||
</td>
|
||||
<td><div class="fw-bold">{{ item.item_code }}</div></td>
|
||||
<td>
|
||||
<div class="fw-bold">{{ item.item_name }}</div>
|
||||
{% if item.description %}<div class="text-muted small">{{ item.description|truncatechars:80 }}</div>{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.get_category_display %}
|
||||
<span class="badge bg-secondary">{{ item.get_category_display }}</span>
|
||||
{% else %}-{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge
|
||||
bg-{% if item.item_type == 'CONSUMABLE' %}primary
|
||||
{% elif item.item_type == 'REUSABLE' %}success
|
||||
{% elif item.item_type == 'EQUIPMENT' %}info
|
||||
{% elif item.item_type == 'MEDICATION' %}warning
|
||||
{% elif item.item_type == 'DEVICE' %}danger
|
||||
{% elif item.item_type == 'REUSABLE' %}success
|
||||
{% else %}secondary
|
||||
{% endif %}">
|
||||
{{ item.get_item_type_display }}</span>
|
||||
</td>
|
||||
<td>{{ item.manufacturer.name|default:item.manufacturer|default:"-" }}</td>
|
||||
<td>
|
||||
{% with cs=item.current_stock|default:0 rp=item.reorder_point|default:0 %}
|
||||
{% if cs|floatformat:0 <= 0 %}
|
||||
<span class="text-danger fw-bold">{{ cs }} {{ item.get_unit_of_measure_display }}</span>
|
||||
{% elif cs|floatformat:0 <= rp|floatformat:0 %}
|
||||
<span class="text-warning fw-bold">{{ cs }} {{ item.get_unit_of_measure_display }}</span>
|
||||
{% else %}
|
||||
<span>{{ cs }} {{ item.get_unit_of_measure_display }}</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td>${{ item.unit_cost|default:0|floatformat:2|intcomma }}</td>
|
||||
<td>
|
||||
{% with tv=item.total_value|default:item.current_stock|default:0|floatformat:2 %}
|
||||
{% if item.total_value %}
|
||||
${{ item.total_value|floatformat:2|intcomma }}
|
||||
{% else %}
|
||||
${{ item.current_stock|default:0|floatformat:0|add:"0"|floatformat:0|intcomma }}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td>
|
||||
{# {% if item.get_stock_status_display %}#}
|
||||
{# {% with s=item.stock_status %}#}
|
||||
{# {% if s == 'in_stock' %}{% setvar 'success' as ss %}{% elif s == 'low_stock' %}{% setvar 'warning' as ss %}#}
|
||||
{# {% elif s == 'out_of_stock' %}{% setvar 'danger' as ss %}{% elif s == 'needs_reorder' %}{% setvar 'info' as ss %}#}
|
||||
{# {% else %}{% setvar 'secondary' as ss %}{% endif %}#}
|
||||
{# <span class="badge bg-{{ ss|default:'secondary' }}">{{ item.get_stock_status_display }}</span>#}
|
||||
{# {% endwith %}#}
|
||||
{# {% else %}-{% endif %}#}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a class="btn btn-outline-primary" href="{% url 'inventory:item_detail' item.id %}" title="View">
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'inventory:item_update' item.id %}" title="Edit">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
<a class="btn btn-outline-info" href="{% url 'inventory:stock_list' %}?item={{ item.id }}" title="Stock">
|
||||
<i class="fa fa-boxes"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="11" class="text-center">No items found.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if is_paginated %}
|
||||
{% include 'partial/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -275,9 +364,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="stock-alerts-content">
|
||||
<!-- Content will be loaded dynamically -->
|
||||
</div>
|
||||
<div id="stock-alerts-content"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
@ -295,9 +382,7 @@
|
||||
<h5 class="modal-title">Item Quick View</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="item-quick-view-content">
|
||||
<!-- Content will be loaded dynamically -->
|
||||
</div>
|
||||
<div class="modal-body" id="item-quick-view-content"></div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<a href="#" class="btn btn-primary" id="view-full-item">View Full Details</a>
|
||||
@ -308,323 +393,97 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'plugins/datatables.net/js/dataTables.min.js' %}"></script>
|
||||
<script src="{% static 'plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
|
||||
<script src="{% static 'plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
|
||||
<script src="{% static 'plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
|
||||
<script src="{% static 'plugins/select2/dist/js/select2.min.js' %}"></script>
|
||||
<script src="{% static 'plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var table;
|
||||
(function() {
|
||||
// Select2
|
||||
$('.select2').select2({ theme: 'bootstrap-5', width: '100%' });
|
||||
|
||||
// Initialize DataTable
|
||||
table = $('#items-table').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
ajax: {
|
||||
url: '',
|
||||
data: function(d) {
|
||||
d.search_value = $('#search-input').val();
|
||||
d.category = $('#category-filter').val();
|
||||
d.item_type = $('#type-filter').val();
|
||||
d.stock_status = $('#stock-filter').val();
|
||||
d.manufacturer = $('#manufacturer-filter').val();
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
data: 'id',
|
||||
orderable: false,
|
||||
render: function(data, type, row) {
|
||||
return '<div class="form-check"><input class="form-check-input item-checkbox" type="checkbox" value="' + data + '"></div>';
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'item_code',
|
||||
render: function(data, type, row) {
|
||||
return '<div class="fw-bold">' + data + '</div>';
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'item_name',
|
||||
render: function(data, type, row) {
|
||||
return '<div>' +
|
||||
'<div class="fw-bold">' + data + '</div>' +
|
||||
(row.description ? '<div class="text-muted small">' + row.description.substring(0, 50) + '...</div>' : '') +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'category',
|
||||
render: function(data, type, row) {
|
||||
return '<span class="badge bg-secondary">' + row.category_display + '</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'item_type',
|
||||
render: function(data, type, row) {
|
||||
var colors = {
|
||||
'CONSUMABLE': 'primary',
|
||||
'REUSABLE': 'success',
|
||||
'EQUIPMENT': 'info',
|
||||
'MEDICATION': 'warning',
|
||||
'DEVICE': 'danger'
|
||||
// Auto-submit filters on change (except search which uses the button, but still submit on Enter)
|
||||
$('#category-filter, #type-filter, #stock-filter, #manufacturer-filter').on('change', function() {
|
||||
document.getElementById('filter-form').submit();
|
||||
});
|
||||
|
||||
// Select all / individual
|
||||
const selectAll = document.getElementById('select-all');
|
||||
const toggleBulk = () => {
|
||||
const checkedCount = document.querySelectorAll('.row-checkbox:checked').length;
|
||||
document.getElementById('bulk-actions-btn').disabled = checkedCount === 0;
|
||||
document.getElementById('bulk-dropdown').disabled = checkedCount === 0;
|
||||
};
|
||||
return '<span class="badge bg-' + (colors[data] || 'secondary') + '">' + row.item_type_display + '</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'manufacturer',
|
||||
render: function(data, type, row) {
|
||||
return data || '<span class="text-muted">-</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'current_stock',
|
||||
render: function(data, type, row) {
|
||||
var className = '';
|
||||
if (data <= 0) {
|
||||
className = 'text-danger fw-bold';
|
||||
} else if (data <= row.reorder_point) {
|
||||
className = 'text-warning fw-bold';
|
||||
}
|
||||
return '<span class="' + className + '">' + data + ' ' + row.unit_of_measure_display + '</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'unit_cost',
|
||||
render: function(data, type, row) {
|
||||
return '$' + parseFloat(data).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'total_value',
|
||||
render: function(data, type, row) {
|
||||
return '$' + parseFloat(data).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'stock_status',
|
||||
render: function(data, type, row) {
|
||||
var badges = {
|
||||
'in_stock': 'bg-success',
|
||||
'low_stock': 'bg-warning',
|
||||
'out_of_stock': 'bg-danger',
|
||||
'needs_reorder': 'bg-info'
|
||||
};
|
||||
return '<span class="badge ' + (badges[data] || 'bg-secondary') + '">' + row.stock_status_display + '</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'id',
|
||||
orderable: false,
|
||||
render: function(data, type, row) {
|
||||
return '<div class="btn-group btn-group-sm">' +
|
||||
'<button class="btn btn-outline-primary" onclick="viewItem(' + data + ')" title="View">' +
|
||||
'<i class="fa fa-eye"></i></button>' +
|
||||
'<button class="btn btn-outline-secondary" onclick="editItem(' + data + ')" title="Edit">' +
|
||||
'<i class="fa fa-edit"></i></button>' +
|
||||
'<button class="btn btn-outline-info" onclick="viewStock(' + data + ')" title="Stock">' +
|
||||
'<i class="fa fa-boxes"></i></button>' +
|
||||
'</div>';
|
||||
}
|
||||
}
|
||||
],
|
||||
order: [[1, 'asc']],
|
||||
pageLength: 25,
|
||||
responsive: true,
|
||||
language: {
|
||||
processing: '<div class="d-flex justify-content-center"><div class="spinner-border" role="status"></div></div>'
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize Select2
|
||||
$('.select2').select2({
|
||||
theme: 'bootstrap-5',
|
||||
width: '100%'
|
||||
});
|
||||
|
||||
// Load filter options and stats
|
||||
loadFilterOptions();
|
||||
loadStats();
|
||||
|
||||
// Filter event handlers
|
||||
$('#search-input, #category-filter, #type-filter, #stock-filter, #manufacturer-filter').on('change keyup', function() {
|
||||
table.draw();
|
||||
});
|
||||
|
||||
// Select all checkbox
|
||||
$('#select-all').on('change', function() {
|
||||
$('.item-checkbox').prop('checked', this.checked);
|
||||
updateBulkActions();
|
||||
});
|
||||
|
||||
// Individual checkbox change
|
||||
$(document).on('change', '.item-checkbox', function() {
|
||||
updateBulkActions();
|
||||
});
|
||||
|
||||
// Auto-refresh every 5 minutes
|
||||
setInterval(function() {
|
||||
table.ajax.reload(null, false);
|
||||
loadStats();
|
||||
}, 300000);
|
||||
});
|
||||
|
||||
function loadFilterOptions() {
|
||||
// Load manufacturer options
|
||||
$.ajax({
|
||||
url: '',
|
||||
success: function(data) {
|
||||
var select = $('#manufacturer-filter');
|
||||
data.forEach(function(manufacturer) {
|
||||
select.append('<option value="' + manufacturer + '">' + manufacturer + '</option>');
|
||||
if (selectAll) {
|
||||
selectAll.addEventListener('change', function() {
|
||||
document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = selectAll.checked);
|
||||
toggleBulk();
|
||||
});
|
||||
}
|
||||
document.addEventListener('change', function(e) {
|
||||
if (e.target.classList && e.target.classList.contains('row-checkbox')) toggleBulk();
|
||||
});
|
||||
}
|
||||
|
||||
function loadStats() {
|
||||
$.ajax({
|
||||
url: '{% url "inventory:inventory_stats" %}',
|
||||
success: function(data) {
|
||||
$('#total-items').text(data.total_items);
|
||||
$('#low-stock-count').text(data.low_stock_count);
|
||||
$('#out-of-stock-count').text(data.out_of_stock_count);
|
||||
$('#total-value').text('$' + parseFloat(data.total_value).toLocaleString());
|
||||
}
|
||||
// Bulk actions (placeholders unless you wire endpoints)
|
||||
const getSelectedIds = () => Array.from(document.querySelectorAll('.row-checkbox:checked')).map(x => x.value);
|
||||
|
||||
document.getElementById('bulk-update-category').addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const ids = getSelectedIds();
|
||||
if (!ids.length) { toastr && toastr.warning('Please select items to update'); return; }
|
||||
toastr && toastr.info('Bulk category update functionality will be implemented');
|
||||
});
|
||||
}
|
||||
|
||||
function updateBulkActions() {
|
||||
var checkedCount = $('.item-checkbox:checked').length;
|
||||
$('#bulk-actions-btn, #bulk-dropdown').prop('disabled', checkedCount === 0);
|
||||
}
|
||||
document.getElementById('bulk-update-supplier').addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const ids = getSelectedIds();
|
||||
if (!ids.length) { toastr && toastr.warning('Please select items to update'); return; }
|
||||
toastr && toastr.info('Bulk supplier update functionality will be implemented');
|
||||
});
|
||||
|
||||
function viewItem(itemId) {
|
||||
window.location.href = '{% url "inventory:item_detail" 0 %}'.replace('0', itemId);
|
||||
}
|
||||
|
||||
function editItem(itemId) {
|
||||
window.location.href = '{% url "inventory:item_update" 0 %}'.replace('0', itemId);
|
||||
}
|
||||
|
||||
function viewStock(itemId) {
|
||||
window.location.href = '{% url "inventory:stock_list" %}?item=' + itemId;
|
||||
}
|
||||
|
||||
function bulkUpdateCategory() {
|
||||
var selectedIds = $('.item-checkbox:checked').map(function() {
|
||||
return this.value;
|
||||
}).get();
|
||||
|
||||
if (selectedIds.length === 0) {
|
||||
toastr.warning('Please select items to update');
|
||||
return;
|
||||
}
|
||||
|
||||
// Implementation for bulk category update
|
||||
toastr.info('Bulk category update functionality will be implemented');
|
||||
}
|
||||
|
||||
function bulkUpdateSupplier() {
|
||||
var selectedIds = $('.item-checkbox:checked').map(function() {
|
||||
return this.value;
|
||||
}).get();
|
||||
|
||||
if (selectedIds.length === 0) {
|
||||
toastr.warning('Please select items to update');
|
||||
return;
|
||||
}
|
||||
|
||||
// Implementation for bulk supplier update
|
||||
toastr.info('Bulk supplier update functionality will be implemented');
|
||||
}
|
||||
|
||||
{#function bulkExport() {#}
|
||||
{# var selectedIds = $('.item-checkbox:checked').map(function() {#}
|
||||
{# return this.value;#}
|
||||
{# }).get();#}
|
||||
{#document.getElementById('bulk-deactivate').addEventListener('click', function(e) {#}
|
||||
{# e.preventDefault();#}
|
||||
{# const ids = getSelectedIds();#}
|
||||
{# if (!ids.length) { toastr && toastr.warning('Please select items to deactivate'); return; }#}
|
||||
{# if (!confirm('Are you sure you want to deactivate ' + ids.length + ' item(s)?')) return;#}
|
||||
{##}
|
||||
{# if (selectedIds.length === 0) {#}
|
||||
{# toastr.warning('Please select items to export');#}
|
||||
{# return;#}
|
||||
{# }#}
|
||||
{# #}
|
||||
{# window.location.href = '{% url "inventory:item_export" %}?ids=' + selectedIds.join(',');#}
|
||||
{# }#}
|
||||
|
||||
function bulkDeactivate() {
|
||||
var selectedIds = $('.item-checkbox:checked').map(function() {
|
||||
return this.value;
|
||||
}).get();
|
||||
|
||||
if (selectedIds.length === 0) {
|
||||
toastr.warning('Please select items to deactivate');
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm('Are you sure you want to deactivate ' + selectedIds.length + ' item(s)?')) {
|
||||
$.ajax({
|
||||
url: '',
|
||||
method: 'POST',
|
||||
data: {
|
||||
'item_ids': selectedIds,
|
||||
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
toastr.success(response.message);
|
||||
$('#items-table').DataTable().ajax.reload();
|
||||
$('#select-all').prop('checked', false);
|
||||
updateBulkActions();
|
||||
} else {
|
||||
toastr.error('Failed to deactivate items');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
toastr.error('An error occurred during bulk deactivation');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
{#function exportData(format) {#}
|
||||
{# window.location.href = '{% url "inventory:item_export" %}?format=' + format;#}
|
||||
{# }#}
|
||||
|
||||
function refreshTable() {
|
||||
$('#items-table').DataTable().ajax.reload();
|
||||
loadStats();
|
||||
toastr.info('Table refreshed');
|
||||
}
|
||||
|
||||
function showStockAlerts() {
|
||||
$('#stockAlertsModal').modal('show');
|
||||
loadStockAlerts();
|
||||
}
|
||||
|
||||
{#function loadStockAlerts() {#}
|
||||
{# $.ajax({#}
|
||||
{# url: '{% url "inventory:stock_alerts_api" %}',#}
|
||||
{# success: function(data) {#}
|
||||
{# $('#stock-alerts-content').html(data.html);#}
|
||||
{# },#}
|
||||
{# error: function() {#}
|
||||
{# toastr.error('Failed to load stock alerts');#}
|
||||
{# fetch("{% url 'inventory:item_bulk_deactivate' %}", {#}
|
||||
{# method: 'POST',#}
|
||||
{# headers: { 'Content-Type': 'application/json', 'X-CSRFToken': '{{ csrf_token }}' },#}
|
||||
{# body: JSON.stringify({ item_ids: ids })#}
|
||||
{# }).then(r => r.json()).then(data => {#}
|
||||
{# if (data.success) {#}
|
||||
{# toastr && toastr.success(data.message || 'Items deactivated');#}
|
||||
{# window.location.reload();#}
|
||||
{# } else {#}
|
||||
{# toastr && toastr.error(data.message || 'Failed to deactivate items');#}
|
||||
{# }#}
|
||||
{# }).catch(() => toastr && toastr.error('An error occurred during bulk deactivation'));#}
|
||||
{# });#}
|
||||
{# } #}
|
||||
|
||||
{#function generateReorderReport() {#}
|
||||
{# window.open('{% url "inventory:reorder_report" %}', '_blank');#}
|
||||
{# }#}
|
||||
|
||||
function createPurchaseOrders() {
|
||||
// Implementation for creating purchase orders from alerts
|
||||
toastr.info('Purchase order creation functionality will be implemented');
|
||||
// Stats (if you still want to refresh via API; otherwise you can remove this block)
|
||||
function loadStats() {
|
||||
fetch('{% url "inventory:inventory_stats" %}')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
document.getElementById('total-items').textContent = data.total_items ?? '-';
|
||||
document.getElementById('low-stock-count').textContent = data.low_stock_count ?? '-';
|
||||
document.getElementById('out-of-stock-count').textContent = data.out_of_stock_count ?? '-';
|
||||
document.getElementById('total-value').textContent = data.total_value ? ('$' + Number(data.total_value).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })) : '-';
|
||||
}).catch(() => {});
|
||||
}
|
||||
// Optional periodic refresh
|
||||
// setInterval(loadStats, 300000);
|
||||
|
||||
{#window.showStockAlerts = function() {#}
|
||||
{# const modal = new bootstrap.Modal(document.getElementById('stockAlertsModal'));#}
|
||||
{# modal.show();#}
|
||||
{# // TODO: fetch and render alerts if you have an API#}
|
||||
{# // fetch('{% url "inventory:stock_alerts_api" %}').then(r => r.json()).then(...);#}
|
||||
{# };#}
|
||||
|
||||
window.createPurchaseOrders = function() {
|
||||
toastr && toastr.info('Purchase order creation functionality will be implemented');
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -465,10 +465,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a class="me-3" href="{% url 'inventory:stock_detail' stock.id %}">
|
||||
<a class="btn btn-xs btn-outline-primary me-1" href="{% url 'inventory:stock_detail' stock.id %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<a class="me-3" href="{% url 'inventory:stock_update' stock.id %}">
|
||||
<a class="btn btn-xs btn-outline-secondary" href="{% url 'inventory:stock_update' stock.id %}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@ -301,44 +301,7 @@
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if is_paginated %}
|
||||
<div class="pagination-wrapper">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-5">
|
||||
<div class="dataTables_info">
|
||||
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ paginator.count }} entries
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-7">
|
||||
<div class="dataTables_paginate">
|
||||
<ul class="pagination">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="paginate_button page-item previous">
|
||||
<a href="?page={{ page_obj.previous_page_number }}" class="page-link">Previous</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% for num in page_obj.paginator.page_range %}
|
||||
{% if page_obj.number == num %}
|
||||
<li class="paginate_button page-item active">
|
||||
<a href="#" class="page-link">{{ num }}</a>
|
||||
</li>
|
||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||
<li class="paginate_button page-item">
|
||||
<a href="?page={{ num }}" class="page-link">{{ num }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="paginate_button page-item next">
|
||||
<a href="?page={{ page_obj.next_page_number }}" class="page-link">Next</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'partial/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user