mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Stock available (#3057)
* Fix display of stock labels - If 'shipped' or 'installed', don't display 'allocated' flag * Switch stock item data around * Add 'available' and 'allocation' information to the StockItem detail page - Cache some context data to the view renderer * Stock table now also displays allocation informatoin
This commit is contained in:
parent
1c6e5f0f20
commit
ea9f2f81f1
@ -156,158 +156,7 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if item.serialized %}
|
||||
<tr>
|
||||
<td><span class='fas fa-hashtag'></span></td>
|
||||
<td>{% trans "Serial Number" %}</td>
|
||||
<td>
|
||||
{{ item.serial }}
|
||||
<div class='btn-group float-right' role='group'>
|
||||
{% if previous %}
|
||||
<a class="btn btn-small btn-outline-secondary" aria-label="{% trans 'previous page' %}" href="{% url request.resolver_match.url_name previous.id %}" title='{% trans "Navigate to previous serial number" %}'>
|
||||
<span class='fas fa-angle-left'></span>
|
||||
<small>{{ previous.serial }}</small>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class='btn btn-small btn-outline-secondary text-sm' href='#' id='serial-number-search' title='{% trans "Search for serial number" %}'>
|
||||
<span class='fas fa-search'></span>
|
||||
</a>
|
||||
{% if next %}
|
||||
<a class="btn btn-small btn-outline-secondary text-sm" aria-label="{% trans 'next page' %}" href="{% url request.resolver_match.url_name next.id %}" title='{% trans "Navigate to next serial number" %}'>
|
||||
<small>{{ next.serial }}</small>
|
||||
<span class='fas fa-angle-right'></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>{% trans "Quantity" %}</td>
|
||||
<td>{% decimal item.quantity %} {% if item.part.units %}{{ item.part.units }}{% endif %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><span class='fas fa-info'></span></td>
|
||||
<td>{% trans "Status" %}</td>
|
||||
<td>{% stock_status_label item.status %}</td>
|
||||
</tr>
|
||||
{% if item.expiry_date %}
|
||||
<tr>
|
||||
<td><span class='fas fa-calendar-alt{% if item.is_expired %} icon-red{% endif %}'></span></td>
|
||||
<td>{% trans "Expiry Date" %}</td>
|
||||
<td>
|
||||
{% render_date item.expiry_date %}
|
||||
{% if item.is_expired %}
|
||||
<span title='{% blocktrans %}This StockItem expired on {{ item.expiry_date }}{% endblocktrans %}' class='badge rounded-pill bg-danger badge-right'>{% trans "Expired" %}</span>
|
||||
{% elif item.is_stale %}
|
||||
<span title='{% blocktrans %}This StockItem expires on {{ item.expiry_date }}{% endblocktrans %}' class='badge rounded-pill bg-warning badge-right'>{% trans "Stale" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><span class='fas fa-calendar-alt'></span></td>
|
||||
<td>{% trans "Last Updated" %}</td>
|
||||
<td>{{ item.updated }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-calendar-alt'></span></td>
|
||||
<td>{% trans "Last Stocktake" %}</td>
|
||||
{% if item.stocktake_date %}
|
||||
<td>{% render_date item.stocktake_date %} <span class='badge badge-right rounded-pill bg-dark'>{{ item.stocktake_user }}</span></td>
|
||||
{% else %}
|
||||
<td><em>{% trans "No stocktake performed" %}</em></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class='info-messages'>
|
||||
|
||||
{% if item.is_building %}
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% trans "This stock item is in production and cannot be edited." %}<br>
|
||||
{% trans "Edit the stock item from the build view." %}<br>
|
||||
|
||||
{% if item.build %}
|
||||
<a href="{% url 'build-detail' item.build.id %}">
|
||||
<strong>{{ item.build }}</strong>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if item.hasRequiredTests and not item.passedAllRequiredTests %}
|
||||
<div class='alert alert-block alert-danger'>
|
||||
{% trans "This stock item has not passed all required tests" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% for allocation in item.sales_order_allocations.all %}
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% object_link 'so-detail' allocation.line.order.id allocation.line.order as link %}
|
||||
{% decimal allocation.quantity as qty %}
|
||||
{% trans "This stock item is allocated to Sales Order" %} {{ link }} {% if qty < item.quantity %}({% trans "Quantity" %}: {{ qty }}){% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% for allocation in item.allocations.all %}
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% object_link 'build-detail' allocation.build.id allocation.build as link %}
|
||||
{% decimal allocation.quantity as qty %}
|
||||
{% trans "This stock item is allocated to Build Order" %} {{ link }} {% if qty < item.quantity %}({% trans "Quantity" %}: {{ qty }}){% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if item.serialized %}
|
||||
<div class='alert alert-block alert-warning'>
|
||||
{% trans "This stock item is serialized - it has a unique serial number and the quantity cannot be adjusted." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock details %}
|
||||
|
||||
{% block details_right %}
|
||||
<table class="table table-striped table-condensed">
|
||||
<col width='25'>
|
||||
|
||||
{% if item.customer %}
|
||||
<tr>
|
||||
<td><span class='fas fa-user-tie'></span></td>
|
||||
<td>{% trans "Customer" %}</td>
|
||||
<td><a href="{% url 'company-detail' item.customer.id %}?display=assigned-stock">{{ item.customer.name }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.belongs_to %}
|
||||
<tr>
|
||||
<td><span class='fas fa-box'></span></td>
|
||||
<td>
|
||||
{% trans "Installed In" %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'stock-item-detail' item.belongs_to.id %}">{{ item.belongs_to }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% elif item.sales_order %}
|
||||
<tr>
|
||||
<td><span class='fas fa-user-tie'></span></td>
|
||||
<td>{% trans "Sales Order" %}</td>
|
||||
<td><a href="{% url 'so-detail' item.sales_order.id %}">{{ item.sales_order.reference }}</a> - <a href="{% url 'company-detail' item.sales_order.customer.id %}">{{ item.sales_order.customer.name }}</a></td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td><span class='fas fa-map-marker-alt'></span></td>
|
||||
<td>{% trans "Location" %}</td>
|
||||
{% if item.location %}
|
||||
<td><a href="{% url 'stock-location-detail' item.location.id %}">{{ item.location.name }}</a></td>
|
||||
{% else %}
|
||||
<td><em>{% trans "No location set" %}</em></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% if item.uid %}
|
||||
<tr>
|
||||
<td><span class='fas fa-barcode'></span></td>
|
||||
@ -322,13 +171,6 @@
|
||||
<td>{{ item.batch }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.packaging %}
|
||||
<tr>
|
||||
<td><span class='fas fa-cube'></span></td>
|
||||
<td>{% trans "Packaging" %}</td>
|
||||
<td>{{ item.packaging }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.build %}
|
||||
<tr>
|
||||
<td><span class='fas fa-tools'></span></td>
|
||||
@ -397,18 +239,11 @@
|
||||
<td><a href="{% url 'supplier-part-detail' item.supplier_part.id %}">{{ item.supplier_part.SKU }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.hasRequiredTests %}
|
||||
{% if item.packaging %}
|
||||
<tr>
|
||||
<td><span class='fas fa-vial'></span></td>
|
||||
<td>{% trans "Tests" %}</td>
|
||||
<td>
|
||||
{{ item.requiredTestStatus.passed }} / {{ item.requiredTestStatus.total }}
|
||||
{% if item.passedAllRequiredTests %}
|
||||
<span class='fas fa-check-circle float-right icon-green'></span>
|
||||
{% else %}
|
||||
<span class='fas fa-times-circle float-right icon-red'></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><span class='fas fa-cube'></span></td>
|
||||
<td>{% trans "Packaging" %}</td>
|
||||
<td>{{ item.packaging }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if ownership_enabled and item_owner %}
|
||||
@ -425,6 +260,199 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
</table>
|
||||
|
||||
<div class='info-messages'>
|
||||
|
||||
{% if item.is_building %}
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% trans "This stock item is in production and cannot be edited." %}<br>
|
||||
{% trans "Edit the stock item from the build view." %}<br>
|
||||
|
||||
{% if item.build %}
|
||||
<a href="{% url 'build-detail' item.build.id %}">
|
||||
<strong>{{ item.build }}</strong>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if item.hasRequiredTests and not item.passedAllRequiredTests %}
|
||||
<div class='alert alert-block alert-danger'>
|
||||
{% trans "This stock item has not passed all required tests" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% for allocation in item.sales_order_allocations.all %}
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% object_link 'so-detail' allocation.line.order.id allocation.line.order as link %}
|
||||
{% decimal allocation.quantity as qty %}
|
||||
{% trans "This stock item is allocated to Sales Order" %} {{ link }} {% if qty < item.quantity %}({% trans "Quantity" %}: {{ qty }}){% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% for allocation in item.allocations.all %}
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% object_link 'build-detail' allocation.build.id allocation.build as link %}
|
||||
{% decimal allocation.quantity as qty %}
|
||||
{% trans "This stock item is allocated to Build Order" %} {{ link }} {% if qty < item.quantity %}({% trans "Quantity" %}: {{ qty }}){% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if item.serialized %}
|
||||
<div class='alert alert-block alert-warning'>
|
||||
{% trans "This stock item is serialized - it has a unique serial number and the quantity cannot be adjusted." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock details %}
|
||||
|
||||
{% block details_right %}
|
||||
<table class="table table-striped table-condensed">
|
||||
<col width='25'>
|
||||
|
||||
<tr>
|
||||
{% if item.serialized %}
|
||||
<td>
|
||||
<h5><span class='fas fa-hashtag'></span></h5>
|
||||
</td>
|
||||
<td>
|
||||
<h5>{% trans "Serial Number" %}</h5>
|
||||
</td>
|
||||
<td><h5>
|
||||
{{ item.serial }}
|
||||
<div class='btn-group float-right' role='group'>
|
||||
{% if previous %}
|
||||
<a class="btn btn-small btn-outline-secondary" aria-label="{% trans 'previous page' %}" href="{% url request.resolver_match.url_name previous.id %}" title='{% trans "Navigate to previous serial number" %}'>
|
||||
<span class='fas fa-angle-left'></span>
|
||||
<small>{{ previous.serial }}</small>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class='btn btn-small btn-outline-secondary text-sm' href='#' id='serial-number-search' title='{% trans "Search for serial number" %}'>
|
||||
<span class='fas fa-search'></span>
|
||||
</a>
|
||||
{% if next %}
|
||||
<a class="btn btn-small btn-outline-secondary text-sm" aria-label="{% trans 'next page' %}" href="{% url request.resolver_match.url_name next.id %}" title='{% trans "Navigate to next serial number" %}'>
|
||||
<small>{{ next.serial }}</small>
|
||||
<span class='fas fa-angle-right'></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</h5>
|
||||
</td>
|
||||
{% else %}
|
||||
<td>
|
||||
<h5><div class='fas fa-boxes'></div></h5>
|
||||
</td>
|
||||
<td>
|
||||
<h5>{% trans "Available Quantity" %}</h5>
|
||||
</td>
|
||||
<td>
|
||||
<h5>{% if item.quantity != available %}{% decimal available %} / {% endif %}{% decimal item.quantity %} {% if item.part.units %}{{ item.part.units }}{% endif %}</h5>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% if item.belongs_to %}
|
||||
<tr>
|
||||
<td><span class='fas fa-box'></span></td>
|
||||
<td>
|
||||
{% trans "Installed In" %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'stock-item-detail' item.belongs_to.id %}">{{ item.belongs_to }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% elif item.sales_order %}
|
||||
<tr>
|
||||
<td><span class='fas fa-user-tie'></span></td>
|
||||
<td>{% trans "Sales Order" %}</td>
|
||||
<td><a href="{% url 'so-detail' item.sales_order.id %}">{{ item.sales_order.reference }}</a> - <a href="{% url 'company-detail' item.sales_order.customer.id %}">{{ item.sales_order.customer.name }}</a></td>
|
||||
</tr>
|
||||
{% else %}
|
||||
{% if allocated_to_sales_orders %}
|
||||
<tr>
|
||||
<td><span class='fas fa-truck'></span></td>
|
||||
<td>{% trans "Allocated to Sales Orders" %}</td>
|
||||
<td>{% decimal allocated_to_sales_orders %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if allocated_to_build_orders %}
|
||||
<tr>
|
||||
<td><span class='fas fa-tools'></span></td>
|
||||
<td>{% trans "Allocated to Build Orders" %}</td>
|
||||
<td>{% decimal allocated_to_build_orders %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><span class='fas fa-map-marker-alt'></span></td>
|
||||
<td>{% trans "Location" %}</td>
|
||||
{% if item.location %}
|
||||
<td><a href="{% url 'stock-location-detail' item.location.id %}">{{ item.location.name }}</a></td>
|
||||
{% elif not item.customer %}
|
||||
<td><em>{% trans "No location set" %}</em></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.customer %}
|
||||
<tr>
|
||||
<td><span class='fas fa-user-tie'></span></td>
|
||||
<td>{% trans "Customer" %}</td>
|
||||
<td><a href="{% url 'company-detail' item.customer.id %}?display=assigned-stock">{{ item.customer.name }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% if item.hasRequiredTests %}
|
||||
<tr>
|
||||
<td><span class='fas fa-vial'></span></td>
|
||||
<td>{% trans "Tests" %}</td>
|
||||
<td>
|
||||
{{ item.requiredTestStatus.passed }} / {{ item.requiredTestStatus.total }}
|
||||
{% if item.passedAllRequiredTests %}
|
||||
<span class='fas fa-check-circle float-right icon-green'></span>
|
||||
{% else %}
|
||||
<span class='fas fa-times-circle float-right icon-red'></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
<tr>
|
||||
<td><span class='fas fa-info'></span></td>
|
||||
<td>{% trans "Status" %}</td>
|
||||
<td>{% stock_status_label item.status %}</td>
|
||||
</tr>
|
||||
{% if item.expiry_date %}
|
||||
<tr>
|
||||
<td><span class='fas fa-calendar-alt{% if item.is_expired %} icon-red{% endif %}'></span></td>
|
||||
<td>{% trans "Expiry Date" %}</td>
|
||||
<td>
|
||||
{% render_date item.expiry_date %}
|
||||
{% if item.is_expired %}
|
||||
<span title='{% blocktrans %}This StockItem expired on {{ item.expiry_date }}{% endblocktrans %}' class='badge rounded-pill bg-danger badge-right'>{% trans "Expired" %}</span>
|
||||
{% elif item.is_stale %}
|
||||
<span title='{% blocktrans %}This StockItem expires on {{ item.expiry_date }}{% endblocktrans %}' class='badge rounded-pill bg-warning badge-right'>{% trans "Stale" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><span class='fas fa-calendar-alt'></span></td>
|
||||
<td>{% trans "Last Updated" %}</td>
|
||||
<td>{{ item.updated }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-calendar-alt'></span></td>
|
||||
<td>{% trans "Last Stocktake" %}</td>
|
||||
{% if item.stocktake_date %}
|
||||
<td>{% render_date item.stocktake_date %} <span class='badge badge-right rounded-pill bg-dark'>{{ item.stocktake_user }}</span></td>
|
||||
{% else %}
|
||||
<td><em>{% trans "No stocktake performed" %}</em></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
||||
|
||||
</table>
|
||||
{% endblock details_right %}
|
||||
|
||||
|
@ -94,6 +94,12 @@ class StockItemDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView):
|
||||
data['item_owner'] = self.object.get_item_owner()
|
||||
data['user_owns_item'] = self.object.check_ownership(self.request.user)
|
||||
|
||||
# Allocation information
|
||||
data['allocated_to_sales_orders'] = self.object.sales_order_allocation_count()
|
||||
data['allocated_to_build_orders'] = self.object.build_allocation_count()
|
||||
data['allocated_to_orders'] = data['allocated_to_sales_orders'] + data['allocated_to_build_orders']
|
||||
data['available'] = max(0, self.object.quantity - data['allocated_to_orders'])
|
||||
|
||||
return data
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
@ -1698,13 +1698,22 @@ function loadStockTable(table, options) {
|
||||
sortable: true,
|
||||
formatter: function(value, row) {
|
||||
|
||||
var val = parseFloat(value);
|
||||
var val = '';
|
||||
|
||||
var available = Math.max(0, (row.quantity || 0) - (row.allocated || 0));
|
||||
|
||||
// If there is a single unit with a serial number, use the serial number
|
||||
if (row.serial && row.quantity == 1) {
|
||||
// If there is a single unit with a serial number, use the serial number
|
||||
val = '# ' + row.serial;
|
||||
} else if (row.quantity != available) {
|
||||
// Some quantity is available, show available *and* quantity
|
||||
var ava = +parseFloat(available).toFixed(5);
|
||||
var tot = +parseFloat(row.quantity).toFixed(5);
|
||||
|
||||
val = `${ava} / ${tot}`;
|
||||
} else {
|
||||
val = +val.toFixed(5);
|
||||
// Format floating point numbers with this one weird trick
|
||||
val = +parseFloat(value).toFixed(5);
|
||||
}
|
||||
|
||||
var html = renderLink(val, `/stock/item/${row.pk}/`);
|
||||
@ -1719,16 +1728,7 @@ function loadStockTable(table, options) {
|
||||
} else if (row.customer) {
|
||||
// StockItem has been assigned to a customer
|
||||
html += makeIconBadge('fa-user', '{% trans "Stock item assigned to customer" %}');
|
||||
}
|
||||
|
||||
if (row.expired) {
|
||||
html += makeIconBadge('fa-calendar-times icon-red', '{% trans "Stock item has expired" %}');
|
||||
} else if (row.stale) {
|
||||
html += makeIconBadge('fa-stopwatch', '{% trans "Stock item will expire soon" %}');
|
||||
}
|
||||
|
||||
if (row.allocated) {
|
||||
|
||||
} else if (row.allocated) {
|
||||
if (row.serial != null && row.quantity == 1) {
|
||||
html += makeIconBadge('fa-bookmark icon-yellow', '{% trans "Serialized stock item has been allocated" %}');
|
||||
} else if (row.allocated >= row.quantity) {
|
||||
@ -1736,10 +1736,14 @@ function loadStockTable(table, options) {
|
||||
} else {
|
||||
html += makeIconBadge('fa-bookmark', '{% trans "Stock item has been partially allocated" %}');
|
||||
}
|
||||
} else if (row.belongs_to) {
|
||||
html += makeIconBadge('fa-box', '{% trans "Stock item has been installed in another item" %}');
|
||||
}
|
||||
|
||||
if (row.belongs_to) {
|
||||
html += makeIconBadge('fa-box', '{% trans "Stock item has been installed in another item" %}');
|
||||
if (row.expired) {
|
||||
html += makeIconBadge('fa-calendar-times icon-red', '{% trans "Stock item has expired" %}');
|
||||
} else if (row.stale) {
|
||||
html += makeIconBadge('fa-stopwatch', '{% trans "Stock item will expire soon" %}');
|
||||
}
|
||||
|
||||
// Special stock status codes
|
||||
|
Loading…
Reference in New Issue
Block a user