Marwan Alwali b9b8c69129 update
2025-08-31 10:47:23 +03:00

566 lines
24 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}Inventory Stock - 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" />
{% 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">Stock Management</li>
</ol>
<!-- END breadcrumb -->
<!-- BEGIN page-header -->
<h1 class="page-header">
Inventory Stock
<small>Stock Level Management & Tracking</small>
</h1>
<!-- END page-header -->
<!-- BEGIN panel -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Stock Management</h4>
<div class="panel-heading-btn">
<a href="{% url 'inventory:stock_create' %}" class="btn btn-xs btn-warning me-2">
<i class="fa fa-edit"></i> Stock Adjustment
</a>
<a href="{% url 'inventory:stock_create' %}" class="btn btn-xs btn-info me-2">
<i class="fa fa-clipboard-list"></i> Physical Count
</a>
<button class="btn btn-xs btn-primary me-2" data-bs-toggle="modal" data-bs-target="#stock-alerts-modal">
<i class="fa fa-exclamation-triangle"></i> Stock Alerts
</button>
<button class="btn btn-xs btn-success me-2" onclick="exportStock()">
<i class="fa fa-download"></i> Export
</button>
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
</div>
</div>
<div class="panel-body">
<!-- Stock Statistics -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card border-primary">
<div class="card-body text-center">
<div class="fs-24px fw-bold text-primary">{{ total_items }}</div>
<div class="small text-muted">Total Items</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-success">
<div class="card-body text-center">
<div class="fs-24px fw-bold text-success">{{ in_stock_items }}</div>
<div class="small text-muted">In Stock</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-warning">
<div class="card-body text-center">
<div class="fs-24px fw-bold text-warning">{{ low_stock_items }}</div>
<div class="small text-muted">Low Stock</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-danger">
<div class="card-body text-center">
<div class="fs-24px fw-bold text-danger">{{ out_of_stock_items }}</div>
<div class="small text-muted">Out of Stock</div>
</div>
</div>
</div>
</div>
<!-- Quick Filters -->
<div class="row mb-3">
<div class="col-md-12">
<div class="btn-group me-2" role="group">
<button type="button" class="btn btn-outline-primary active" onclick="filterByStock('all')">All Stock</button>
<button type="button" class="btn btn-outline-primary" onclick="filterByStock('in_stock')">In Stock</button>
<button type="button" class="btn btn-outline-primary" onclick="filterByStock('low_stock')">Low Stock</button>
<button type="button" class="btn btn-outline-primary" onclick="filterByStock('out_of_stock')">Out of Stock</button>
<button type="button" class="btn btn-outline-primary" onclick="filterByStock('expired')">Expired</button>
</div>
<div class="btn-group me-2" role="group">
<button type="button" class="btn btn-outline-secondary active" onclick="filterByLocation('all')">All Locations</button>
{% for location in locations %}
<button type="button" class="btn btn-outline-secondary" onclick="filterByLocation('{{ location.id }}')">{{ location.name }}</button>
{% endfor %}
</div>
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-info active" onclick="filterByCategory('all')">All Categories</button>
{% for category in categories %}
<button type="button" class="btn btn-outline-info" onclick="filterByCategory('{{ category }}')">{{ category }}</button>
{% endfor %}
</div>
</div>
</div>
<!-- Search and Actions -->
<div class="row mb-3">
<div class="col-md-6">
<div class="input-group">
<input type="text" class="form-control" id="search-input" placeholder="Search items, lot numbers, locations...">
<button class="btn btn-outline-secondary" type="button" onclick="searchStock()">
<i class="fa fa-search"></i>
</button>
</div>
</div>
<div class="col-md-6 text-end">
<div class="form-check form-switch d-inline-block me-3">
<input class="form-check-input" type="checkbox" id="show-expired">
<label class="form-check-label" for="show-expired">
Show expired items
</label>
</div>
<div class="form-check form-switch d-inline-block">
<input class="form-check-input" type="checkbox" id="auto-refresh" checked>
<label class="form-check-label" for="auto-refresh">
Auto refresh
</label>
</div>
</div>
</div>
<!-- Stock Table -->
<div class="table-responsive">
<table id="stock-table" class="table table-striped table-bordered align-middle">
<thead>
<tr>
<th>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="select-all">
</div>
</th>
<th>Item</th>
<th>Location</th>
<th>Lot Number</th>
<th>Current Stock</th>
<th>Available</th>
<th>Expiry Date</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for stock in object_list %}
<tr data-stock-status="{{ stock.stock_status }}" data-location="{{ stock.location.id }}" data-category="{{ stock.inventory_item.category }}">
<td>
<div class="form-check">
<input class="form-check-input row-checkbox" type="checkbox" value="{{ stock.stock_id }}">
</div>
</td>
<td>
<div class="d-flex align-items-center">
{% if stock.inventory_item.image %}
<img src="{{ stock.inventory_item.image.url }}" alt="{{ stock.inventory_item.name }}" class="rounded me-2" width="40" height="40">
{% else %}
<div class="bg-light rounded me-2 d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
<i class="fa fa-box text-muted"></i>
</div>
{% endif %}
<div>
<div class="fw-bold">{{ stock.inventory_item.item_name }}</div>
<div class="small text-muted">{{ stock.inventory_item.item_code }}</div>
</div>
</div>
</td>
<td>
<div class="fw-bold">{{ stock.location.name }}</div>
<div class="small text-muted">{{ stock.location.location_type }}</div>
</td>
<td>
<code class="small">{{ stock.lot_number|default:"--" }}</code>
</td>
<td>
<div class="fw-bold">{{ stock.quantity_on_hand }}</div>
<div class="small text-muted">{{ stock.inventory_item.unit_of_measure }}</div>
</td>
<td>
<div class="fw-bold">{{ stock.quantity_available }}</div>
<div class="small text-muted">Available</div>
</td>
<td>
{% if stock.expiration_date %}
<div class="{% if stock.is_expired %}text-danger{% elif stock.is_expiring_soon %}text-warning{% endif %}">
{{ stock.expiration_date|date:"M d, Y" }}
</div>
{% if stock.is_expired %}
<div class="small text-danger">Expired</div>
{% elif stock.is_expiring_soon %}
<div class="small text-warning">Expiring Soon</div>
{% endif %}
{% else %}
<span class="text-muted">No expiry</span>
{% endif %}
</td>
<td>
{% if stock.quantity_available >= stock.inventory_item.reorder_point %}
<span class="badge bg-success">AVAILABLE</span><br>
<small class="text-muted"> reorder point {{ stock.inventory_item.reorder_point }}</small>
{% elif stock.quantity_available <= stock.inventory_item.reorder_point %}
<span class="badge bg-warning">LOW STOCK</span><br>
<small class="text-muted"> reorder point {{ stock.inventory_item.reorder_point }}</small>
{% elif stock.quantity_available == 0 %}
<span class="badge bg-danger">OUT OF STOCK</span><br>
<small class="text-muted"> reorder point {{ stock.inventory_item.reorder_point }}</small>
{% else %}
<span class="badge bg-secondary">{{ stock.inventory_item.reorder_point }}</span>
{% endif %}
{% if stock.is_reserved %}
<div class="small mt-1">
<span class="badge bg-info badge-sm">{{ stock.quantity_reserved }} Reserved</span>
</div>
{% endif %}
</td>
<td>
<div class="btn-group btn-group-sm">
<a href="{% url 'inventory:stock_detail' stock.pk %}" class="btn btn-outline-primary btn-sm" title="View Details">
<i class="fa fa-eye"></i>
</a>
<button class="btn btn-outline-success btn-sm" onclick="quickAdjust({{ stock.id }})" title="Quick Adjust">
<i class="fa fa-edit"></i>
</button>
<button class="btn btn-outline-warning btn-sm" onclick="moveStock({{ stock.id }})" title="Move Stock">
<i class="fa fa-arrows-alt"></i>
</button>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="9" class="text-center text-muted py-4">
<i class="fa fa-boxes fa-2x mb-2"></i>
<div>No stock records found</div>
<a href="{% url 'inventory:item_create' %}" class="btn btn-primary mt-2">
<i class="fa fa-plus me-2"></i>Add First Item
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Bulk Actions -->
<div id="bulk-actions" class="mt-3" style="display: none;">
<div class="alert alert-info">
<div class="d-flex justify-content-between align-items-center">
<div>
<strong><span id="selected-count">0</span></strong> item(s) selected
</div>
<div>
<button class="btn btn-sm btn-warning me-2" onclick="bulkAdjust()">
<i class="fa fa-edit me-1"></i>Bulk Adjust
</button>
<button class="btn btn-sm btn-info me-2" onclick="bulkMove()">
<i class="fa fa-arrows-alt me-1"></i>Move Stock
</button>
<button class="btn btn-sm btn-success me-2" onclick="bulkReorder()">
<i class="fa fa-shopping-cart me-1"></i>Create Reorder
</button>
<button class="btn btn-sm btn-secondary" onclick="clearSelection()">
<i class="fa fa-times me-1"></i>Clear Selection
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- END panel -->
<!-- Stock Alerts Modal -->
<div class="modal fade" id="stock-alerts-modal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Stock Alerts</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
{% include 'inventory/stock_alerts.html' %}
</div>
</div>
</div>
</div>
<!-- Quick Adjust Modal -->
<div class="modal fade" id="quick-adjust-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Quick Stock Adjustment</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="quick-adjust-form">
<div class="modal-body">
{% csrf_token %}
<input type="hidden" id="adjust-stock-id" name="stock_id">
<div class="mb-3">
<label class="form-label">Adjustment Type</label>
<select class="form-select" name="adjustment_type" required>
<option value="">Select type...</option>
<option value="ADD">Add Stock</option>
<option value="REMOVE">Remove Stock</option>
<option value="SET">Set Quantity</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Quantity</label>
<input type="number" class="form-control" name="quantity" min="0" step="0.01" required>
</div>
<div class="mb-3">
<label class="form-label">Reason</label>
<select class="form-select" name="reason" required>
<option value="">Select reason...</option>
<option value="RECEIVED">Stock Received</option>
<option value="DISPENSED">Dispensed</option>
<option value="EXPIRED">Expired</option>
<option value="DAMAGED">Damaged</option>
<option value="LOST">Lost</option>
<option value="CORRECTION">Correction</option>
<option value="OTHER">Other</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Notes</label>
<textarea class="form-control" name="notes" rows="2" placeholder="Optional notes..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Apply Adjustment</button>
</div>
</form>
</div>
</div>
</div>
{% 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/dropzone/src/options.js' %}"></script>
<script>
$(document).ready(function() {
// Initialize DataTable
var table = $('#stock-table').DataTable({
responsive: true,
pageLength: 25,
order: [[1, 'asc']], // Sort by item name
columnDefs: [
{ orderable: false, targets: [0, 8] } // Disable sorting for checkbox and actions columns
]
});
// Select all checkbox
$('#select-all').on('change', function() {
$('.row-checkbox').prop('checked', this.checked);
updateBulkActions();
});
// Individual row checkboxes
$(document).on('change', '.row-checkbox', function() {
updateBulkActions();
// Update select all checkbox
var totalCheckboxes = $('.row-checkbox').length;
var checkedCheckboxes = $('.row-checkbox:checked').length;
$('#select-all').prop('checked', totalCheckboxes === checkedCheckboxes);
});
function updateBulkActions() {
var selectedCount = $('.row-checkbox:checked').length;
$('#selected-count').text(selectedCount);
if (selectedCount > 0) {
$('#bulk-actions').show();
} else {
$('#bulk-actions').hide();
}
}
// Auto refresh
setInterval(function() {
if ($('#auto-refresh').is(':checked')) {
location.reload();
}
}, 60000); // Every minute
// Quick adjust form submission
$('#quick-adjust-form').on('submit', function(e) {
e.preventDefault();
$.ajax({
url: '{% url "inventory:stock_create" %}',
method: 'POST',
data: $(this).serialize(),
success: function(response) {
if (response.success) {
toastr.success('Stock adjustment applied successfully');
$('#quick-adjust-modal').modal('hide');
location.reload();
} else {
toastr.error('Failed to apply adjustment: ' + response.error);
}
},
error: function() {
toastr.error('An error occurred while applying the adjustment');
}
});
});
});
function filterByStock(status) {
$('.btn-group button').removeClass('active');
event.target.classList.add('active');
if (status === 'all') {
$('tr[data-stock-status]').show();
} else {
$('tr[data-stock-status]').hide();
$(`tr[data-stock-status="${status.toUpperCase()}"]`).show();
}
}
function filterByLocation(locationId) {
$('.btn-group button').removeClass('active');
event.target.classList.add('active');
if (locationId === 'all') {
$('tr[data-location]').show();
} else {
$('tr[data-location]').hide();
$(`tr[data-location="${locationId}"]`).show();
}
}
function filterByCategory(category) {
$('.btn-group button').removeClass('active');
event.target.classList.add('active');
if (category === 'all') {
$('tr[data-category]').show();
} else {
$('tr[data-category]').hide();
$(`tr[data-category="${category}"]`).show();
}
}
function searchStock() {
var searchTerm = $('#search-input').val().toLowerCase();
if (searchTerm === '') {
$('tbody tr').show();
return;
}
$('tbody tr').each(function() {
var rowText = $(this).text().toLowerCase();
if (rowText.includes(searchTerm)) {
$(this).show();
} else {
$(this).hide();
}
});
}
function quickAdjust(stockId) {
$('#adjust-stock-id').val(stockId);
$('#quick-adjust-modal').modal('show');
}
{#function moveStock(stockId) {#}
{# // Implement stock movement functionality#}
{# window.location.href = '{% url "inventory:stock_move" 0 %}'.replace('0', stockId);#}
{# }#}
function bulkAdjust() {
var selectedIds = $('.row-checkbox:checked').map(function() {
return this.value;
}).get();
if (selectedIds.length === 0) {
toastr.warning('Please select items to adjust');
return;
}
// Redirect to bulk adjustment page
window.location.href = '?ids=' + selectedIds.join(',');
}
function bulkMove() {
var selectedIds = $('.row-checkbox:checked').map(function() {
return this.value;
}).get();
if (selectedIds.length === 0) {
toastr.warning('Please select items to move');
return;
}
// Redirect to bulk move page
window.location.href = '?ids=' + selectedIds.join(',');
}
function bulkReorder() {
var selectedIds = $('.row-checkbox:checked').map(function() {
return this.value;
}).get();
if (selectedIds.length === 0) {
toastr.warning('Please select items to reorder');
return;
}
// Create purchase order for selected items
window.location.href = '{% url "inventory:purchase_order_create" %}?stock_ids=' + selectedIds.join(',');
}
function clearSelection() {
$('.row-checkbox, #select-all').prop('checked', false);
$('#bulk-actions').hide();
}
{#function exportStock() {#}
{# window.open('{% url "inventory:stock_export" %}');#}
{# }#}
// Search on enter key
$('#search-input').on('keypress', function(e) {
if (e.which === 13) {
searchStock();
}
});
// Real-time search
$('#search-input').on('input', function() {
searchStock();
});
</script>
{% endblock %}