548 lines
26 KiB
HTML
548 lines
26 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Stock Details - {{ object.item.name }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div>
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{% url 'inventory:dashboard' %}">Inventory</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'inventory:stock_list' %}">Stock</a></li>
|
|
<li class="breadcrumb-item active">{{ object.inventory_item.item_name }}</li>
|
|
</ol>
|
|
<h1 class="page-header mb-0">Stock Details - {{ object.inventory_item.item_name }}</h1>
|
|
</div>
|
|
<div class="ms-auto">
|
|
<div class="btn-group">
|
|
<a href="{% url 'inventory:stock_update' object.pk %}" class="btn btn-primary">
|
|
<i class="fas fa-edit me-2"></i>Update Stock
|
|
</a>
|
|
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
|
|
<span class="visually-hidden">Toggle Dropdown</span>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="adjustStock('add')">
|
|
<i class="fas fa-plus text-success me-2"></i>Add Stock
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="adjustStock('remove')">
|
|
<i class="fas fa-minus text-warning me-2"></i>Remove Stock
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="transferStock()">
|
|
<i class="fas fa-exchange-alt text-info me-2"></i>Transfer Stock
|
|
</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="#" onclick="generateReport()">
|
|
<i class="fas fa-chart-line me-2"></i>Generate Report
|
|
</a></li>
|
|
<li><a class="dropdown-item text-danger" href="">
|
|
<i class="fas fa-trash me-2"></i>Delete Stock Record
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-xl-8">
|
|
<!-- Stock Information -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-boxes me-2"></i>
|
|
Stock Information
|
|
</h4>
|
|
<div class="card-toolbar">
|
|
<span class="badge bg-{% if object.current_quantity <= object.minimum_quantity %}danger{% elif object.current_quantity <= object.reorder_level %}warning{% else %}success{% endif %} fs-6">
|
|
{% if object.current_quantity <= object.minimum_quantity %}
|
|
Critical Stock
|
|
{% elif object.current_quantity <= object.reorder_level %}
|
|
Low Stock
|
|
{% else %}
|
|
In Stock
|
|
{% endif %}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<table class="table table-borderless">
|
|
<tr>
|
|
<td class="fw-bold">Item:</td>
|
|
<td>
|
|
<a href="{% url 'inventory:item_detail' object.inventory_item.pk %}">
|
|
{{ object.inventory_item.item_name }}
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">SKU:</td>
|
|
<td>{{ object.inventory_item.item_code }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Category:</td>
|
|
<td>{{ object.inventory_item.get_category_display }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Location:</td>
|
|
<td>{{ object.location.name }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Current Quantity:</td>
|
|
<td>
|
|
<span class="fs-4 fw-bold {% if object.quantity_available <= object.inventory_item.min_stock_level %}text-danger{% elif object.quantity_available <= object.inventory_item.reorder_point %}text-warning{% else %}text-success{% endif %}">
|
|
{{ object.quantity_available }}
|
|
</span>
|
|
{{ object.inventory_item.unit_of_measure }}
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<table class="table table-borderless">
|
|
<tr>
|
|
<td class="fw-bold">Minimum Quantity:</td>
|
|
<td>{{ object.inventory_item.min_stock_level }} {{ object.inventory_item.unit_of_measure }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Reorder Level:</td>
|
|
<td>{{ object.inventory_item.reorder_point }} {{ object.inventory_item.unit_of_measure }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Maximum Quantity:</td>
|
|
<td>{{ object.inventory_item.max_stock_level|default:"Not set" }} {{ object.item.unit_of_measure }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Unit Cost:</td>
|
|
<td><span class="symbol">ê</span>{{ object.unit_cost|floatformat:'2g' }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Total Value:</td>
|
|
<td>
|
|
<span class="fs-5 fw-bold text-primary">
|
|
<span class="symbol">ê</span>{{ object.total_cost|floatformat:'2g' }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stock Level Progress Bar -->
|
|
<div class="mt-3">
|
|
<div class="d-flex justify-content-between mb-1">
|
|
<span>Stock Level</span>
|
|
<span>{{ object.quantity_available }} / {{ object.inventory_item.min_stock_level }} {{ object.inventory_item.unit_of_measure }}</span>
|
|
</div>
|
|
<div class="progress">
|
|
{% if object.inventory_item.max_stock_level %}
|
|
{% widthratio object.quantity_available object.inventory_item.max_stock_level 100 as percentage %}
|
|
{% else %}
|
|
{% widthratio object.quantity_available object.inventory_item.min_stock_level 100 as percentage %}
|
|
{% endif %}
|
|
<div class="progress-bar bg-{% if object.quantity_available <= object.inventory_item.min_stock_level %}danger{% elif object.quantity_available <= object.inventory_item.reorder_point %}warning{% else %}success{% endif %}"
|
|
style="width: {{ percentage|default:0 }}%"></div>
|
|
</div>
|
|
<div class="d-flex justify-content-between mt-1">
|
|
<small class="text-danger">Min: {{ object.inventory_item.min_stock_level }}</small>
|
|
<small class="text-warning">Reorder: {{ object.inventory_item.reorder_point }}</small>
|
|
{% if object.maximum_quantity %}
|
|
<small class="text-success">Max: {{ object.inventory_item.max_stock_level }}</small>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
{% if object.notes %}
|
|
<div class="mt-3">
|
|
<h6>Notes:</h6>
|
|
<div class="alert alert-info">
|
|
{{ object.notes }}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Transactions -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-history me-2"></i>
|
|
Recent Transactions
|
|
</h4>
|
|
<div class="card-toolbar">
|
|
<button type="button" class="btn btn-sm btn-outline-primary" onclick="refreshTransactions()">
|
|
<i class="fas fa-sync me-2"></i>Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if recent_transactions %}
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>Type</th>
|
|
<th>Quantity</th>
|
|
<th>Balance</th>
|
|
<th>Reference</th>
|
|
<th>User</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for transaction in recent_transactions %}
|
|
<tr>
|
|
<td>
|
|
<div>{{ transaction.date|date:"M d, Y" }}</div>
|
|
<div class="small text-muted">{{ transaction.date|time:"g:i A" }}</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if transaction.transaction_type == 'in' %}success{% elif transaction.transaction_type == 'out' %}warning{% elif transaction.transaction_type == 'adjustment' %}info{% else %}secondary{% endif %}">
|
|
{{ transaction.get_transaction_type_display }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="{% if transaction.transaction_type == 'in' %}text-success{% elif transaction.transaction_type == 'out' %}text-warning{% endif %}">
|
|
{% if transaction.transaction_type == 'in' %}+{% elif transaction.transaction_type == 'out' %}-{% endif %}{{ transaction.quantity }}
|
|
</span>
|
|
</td>
|
|
<td>{{ transaction.balance_after }}</td>
|
|
<td>
|
|
{% if transaction.reference %}
|
|
{{ transaction.reference }}
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ transaction.user.get_full_name|default:"System" }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="text-center mt-3">
|
|
<a href="#" class="btn btn-outline-primary" onclick="viewAllTransactions()">
|
|
<i class="fas fa-list me-2"></i>View All Transactions
|
|
</a>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center text-muted py-4">
|
|
<i class="fas fa-history fa-3x mb-3"></i>
|
|
<h5>No Transactions</h5>
|
|
<p>No transaction history available for this stock item.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Expiry Information -->
|
|
{% if expiring_batches %}
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-calendar-times me-2"></i>
|
|
Expiring Batches
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Batch Number</th>
|
|
<th>Quantity</th>
|
|
<th>Expiry Date</th>
|
|
<th>Days Left</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for batch in expiring_batches %}
|
|
<tr>
|
|
<td>{{ batch.batch_number }}</td>
|
|
<td>{{ batch.quantity }} {{ object.item.unit_of_measure }}</td>
|
|
<td>{{ batch.expiry_date|date:"M d, Y" }}</td>
|
|
<td>
|
|
<span class="{% if batch.days_until_expiry <= 7 %}text-danger{% elif batch.days_until_expiry <= 30 %}text-warning{% else %}text-success{% endif %}">
|
|
{{ batch.days_until_expiry }} days
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if batch.days_until_expiry <= 0 %}danger{% elif batch.days_until_expiry <= 7 %}warning{% elif batch.days_until_expiry <= 30 %}info{% else %}success{% endif %}">
|
|
{% if batch.days_until_expiry <= 0 %}
|
|
Expired
|
|
{% elif batch.days_until_expiry <= 7 %}
|
|
Critical
|
|
{% elif batch.days_until_expiry <= 30 %}
|
|
Warning
|
|
{% else %}
|
|
Good
|
|
{% endif %}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="col-xl-4">
|
|
<!-- Quick Actions -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-bolt me-2"></i>
|
|
Quick Actions
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<button type="button" class="btn btn-success" onclick="adjustStock('add')">
|
|
<i class="fas fa-plus me-2"></i>Add Stock
|
|
</button>
|
|
<button type="button" class="btn btn-warning" onclick="adjustStock('remove')">
|
|
<i class="fas fa-minus me-2"></i>Remove Stock
|
|
</button>
|
|
<button type="button" class="btn btn-info" onclick="transferStock()">
|
|
<i class="fas fa-exchange-alt me-2"></i>Transfer Stock
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="createOrder()">
|
|
<i class="fas fa-shopping-cart me-2"></i>Create Order
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="printLabel()">
|
|
<i class="fas fa-print me-2"></i>Print Label
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stock Statistics -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-chart-bar me-2"></i>
|
|
Statistics (30 days)
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row text-center">
|
|
<div class="col-6">
|
|
<div class="border-end">
|
|
<div class="fs-4 fw-bold text-success">{{ stock_stats.total_in|default:0 }}</div>
|
|
<div class="small text-muted">Stock In</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="fs-4 fw-bold text-warning">{{ stock_stats.total_out|default:0 }}</div>
|
|
<div class="small text-muted">Stock Out</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<div class="mb-3">
|
|
<div class="d-flex justify-content-between mb-1">
|
|
<span>Turnover Rate</span>
|
|
<span>{{ stock_stats.turnover_rate|default:0 }}%</span>
|
|
</div>
|
|
<div class="progress">
|
|
<div class="progress-bar bg-info" style="width: {{ stock_stats.turnover_rate|default:0 }}%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-2">
|
|
<strong>Average Daily Usage:</strong> {{ stock_stats.avg_daily_usage|default:0 }} {{ object.item.unit_of_measure }}
|
|
</div>
|
|
<div>
|
|
<strong>Days of Stock:</strong> {{ stock_stats.days_of_stock|default:0 }} days
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Supplier Information -->
|
|
{% if object.item.suppliers.exists %}
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-truck me-2"></i>
|
|
Suppliers
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% for supplier in object.item.suppliers.all %}
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div class="flex-grow-1">
|
|
<div class="fw-bold">
|
|
<a href="{% url 'inventory:supplier_detail' supplier.pk %}">
|
|
{{ supplier.name }}
|
|
</a>
|
|
</div>
|
|
<div class="small text-muted">{{ supplier.contact_person }}</div>
|
|
<div class="small text-muted">{{ supplier.phone }}</div>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<button type="button" class="btn btn-sm btn-outline-primary" onclick="contactSupplier('{{ supplier.pk }}')">
|
|
<i class="fas fa-phone"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Alerts -->
|
|
{% if alerts %}
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
Alerts
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% for alert in alerts %}
|
|
<div class="alert alert-{% if alert.severity == 'critical' %}danger{% elif alert.severity == 'warning' %}warning{% else %}info{% endif %} alert-dismissible fade show" role="alert">
|
|
<strong>{{ alert.get_severity_display }}:</strong> {{ alert.message }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Location Information -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-map-marker-alt me-2"></i>
|
|
Location Details
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-2">
|
|
<strong>Location:</strong> {{ object.location.name }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Zone:</strong> {{ object.location.zone|default:"Not specified" }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Aisle:</strong> {{ object.location.aisle|default:"Not specified" }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Shelf:</strong> {{ object.location.shelf|default:"Not specified" }}
|
|
</div>
|
|
<div>
|
|
<strong>Temperature:</strong> {{ object.location.temperature_range|default:"Room temperature" }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stock Adjustment Modal -->
|
|
<div class="modal fade" id="stockAdjustmentModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Stock Adjustment</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="stockAdjustmentForm">
|
|
{% csrf_token %}
|
|
<input type="hidden" id="adjustmentType" name="adjustment_type">
|
|
<div class="mb-3">
|
|
<label for="adjustmentQuantity" class="form-label">Quantity</label>
|
|
<input type="number" class="form-control" id="adjustmentQuantity" name="quantity" min="1" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="adjustmentReason" class="form-label">Reason</label>
|
|
<select class="form-select" id="adjustmentReason" name="reason" required>
|
|
<option value="">Select reason</option>
|
|
<option value="received">Stock Received</option>
|
|
<option value="dispensed">Stock Dispensed</option>
|
|
<option value="damaged">Damaged/Expired</option>
|
|
<option value="lost">Lost/Stolen</option>
|
|
<option value="correction">Inventory Correction</option>
|
|
<option value="transfer">Transfer</option>
|
|
<option value="other">Other</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="adjustmentNotes" class="form-label">Notes</label>
|
|
<textarea class="form-control" id="adjustmentNotes" name="notes" rows="3"></textarea>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary" onclick="confirmAdjustment()">Confirm Adjustment</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function adjustStock(type) {
|
|
document.getElementById('adjustmentType').value = type;
|
|
const modal = new bootstrap.Modal(document.getElementById('stockAdjustmentModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function confirmAdjustment() {
|
|
const form = document.getElementById('stockAdjustmentForm');
|
|
const formData = new FormData(form);
|
|
|
|
// In a real implementation, this would submit via AJAX
|
|
console.log('Stock adjustment:', formData.get('adjustment_type'), formData.get('quantity'));
|
|
|
|
// Close modal and refresh page
|
|
bootstrap.Modal.getInstance(document.getElementById('stockAdjustmentModal')).hide();
|
|
location.reload();
|
|
}
|
|
|
|
function transferStock() {
|
|
// In a real implementation, this would open a transfer modal
|
|
alert('Stock transfer functionality would be implemented here.');
|
|
}
|
|
|
|
function createOrder() {
|
|
// In a real implementation, this would create a purchase order
|
|
alert('Purchase order creation would be implemented here.');
|
|
}
|
|
|
|
function printLabel() {
|
|
// In a real implementation, this would print a stock label
|
|
window.print();
|
|
}
|
|
|
|
function contactSupplier(supplierId) {
|
|
// In a real implementation, this would open supplier contact modal
|
|
alert('Supplier contact functionality would be implemented here.');
|
|
}
|
|
|
|
function refreshTransactions() {
|
|
// In a real implementation, this would refresh transactions via AJAX
|
|
location.reload();
|
|
}
|
|
|
|
function viewAllTransactions() {
|
|
// In a real implementation, this would show all transactions
|
|
alert('Full transaction history would be displayed here.');
|
|
}
|
|
|
|
function generateReport() {
|
|
// In a real implementation, this would generate a stock report
|
|
alert('Stock report generation would be implemented here.');
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|