Marwan Alwali 1f0a6bff5f update
2025-08-31 12:21:16 +03:00

489 lines
23 KiB
HTML

{% extends "base.html" %}
{% load allauth %}
{% load static humanize %}
{% block title %}Inventory Items - Inventory Management{% endblock %}
{% block css %}
<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 %}
{% block content %}
<!-- BEGIN breadcrumb -->
<ol class="breadcrumb float-xl-end">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'inventory:dashboard' %}">Inventory</a></li>
<li class="breadcrumb-item active">Items</li>
</ol>
<!-- END breadcrumb -->
<!-- BEGIN page-header -->
<h1 class="page-header">
Inventory Items
<small>Medical Supplies & Equipment Catalog</small>
</h1>
<!-- END page-header -->
<!-- BEGIN panel -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Inventory Items Management</h4>
<div class="panel-heading-btn">
<a href="{% url 'inventory:item_create' %}" class="btn btn-xs btn-success me-2">
<i class="fa fa-plus"></i> Add Item
</a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<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 (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" 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" 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" 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" 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" name="manufacturer" id="manufacturer-filter" data-current="{{ request.GET.manufacturer|default_if_none:'' }}">
<option value="">All Manufacturers</option>
{% 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>
</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 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">{{ 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>
</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 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">{{ 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>
</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 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">{{ 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>
</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 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">
{% 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>
</div>
</div>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary" id="bulk-actions-btn" disabled>
<i class="fa fa-tasks me-2"></i>Bulk Actions
</button>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" id="bulk-dropdown" disabled>
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<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="#" 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="{% 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">
<a class="btn btn-outline-secondary" href="?{{ request.GET.urlencode }}">
<i class="fa fa-refresh me-2"></i>Refresh
</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="{% 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 (regular table) -->
<div class="table-responsive">
<table class="table table-striped table-bordered align-middle">
<thead>
<tr>
<th width="30">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="select-all">
</div>
</th>
<th>Item Code</th>
<th>Item Name</th>
<th>Category</th>
<th>Type</th>
<th>Manufacturer</th>
<th>Current Stock</th>
<th>Unit Cost</th>
<th>Total Value</th>
<th>Status</th>
<th width="140">Actions</th>
</tr>
</thead>
<tbody>
{% 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>
<!-- END panel -->
<!-- Stock Alerts Modal -->
<div class="modal fade" id="stockAlertsModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Stock Level Alerts</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row mb-3">
<div class="col-md-4">
<select class="form-select" id="alert-type-filter">
<option value="">All Alerts</option>
<option value="low_stock">Low Stock</option>
<option value="out_of_stock">Out of Stock</option>
<option value="needs_reorder">Needs Reorder</option>
<option value="expiring_soon">Expiring Soon</option>
</select>
</div>
<div class="col-md-4">
<select class="form-select" id="alert-priority-filter">
<option value="">All Priorities</option>
<option value="critical">Critical</option>
<option value="high">High</option>
<option value="medium">Medium</option>
<option value="low">Low</option>
</select>
</div>
<div class="col-md-4">
<button class="btn btn-primary" onclick="generateReorderReport()">
<i class="fa fa-shopping-cart me-2"></i>Generate Reorder Report
</button>
</div>
</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>
<button type="button" class="btn btn-primary" onclick="createPurchaseOrders()">Create Purchase Orders</button>
</div>
</div>
</div>
</div>
<!-- Quick View Modal -->
<div class="modal fade" id="itemQuickViewModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<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"></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>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<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>
(function() {
// Select2
$('.select2').select2({ theme: 'bootstrap-5', width: '100%' });
// 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;
};
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();
});
// 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');
});
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');
});
{#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;#}
{##}
{# 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'));#}
{# });#}
// 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 %}