diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js
index 94ffbc5333..47cb33100b 100644
--- a/InvenTree/templates/js/translated/part.js
+++ b/InvenTree/templates/js/translated/part.js
@@ -584,54 +584,87 @@ function duplicateBom(part_id, options={}) {
* Construct a "badge" label showing stock information for this particular part
*/
function partStockLabel(part, options={}) {
+ var classes = options.classes || '';
// Prevent literal string 'null' from being displayed
var units = part.units || '';
+ var text = '';
+
+ // Check for stock
if (part.in_stock) {
// There IS stock available for this part
// Is stock "low" (below the 'minimum_stock' quantity)?
if ((part.minimum_stock > 0) && (part.minimum_stock > part.in_stock)) {
- return `{% trans "Low stock" %}: ${part.in_stock} ${units}`;
+ text += `{% trans "Low stock" %}: ${part.in_stock}`;
} else if (part.unallocated_stock == 0) {
- if (part.ordering) {
- // There is no available stock, but stock is on order
- return `{% trans "On Order" %}: ${part.ordering} ${units}`;
- } else if (part.building) {
- // There is no available stock, but stock is being built
- return `{% trans "Building" %}: ${part.building} ${units}`;
- } else {
- // There is no available stock at all
- return `{% trans "No stock available" %}`;
- }
+ // There is no available stock at all
+ text += `{% trans "No stock available" %}`;
} else if (part.unallocated_stock < part.in_stock) {
// Unallocated quanttiy is less than total quantity
- return `{% trans "Available" %}: ${part.unallocated_stock}/${part.in_stock} ${units}`;
+ text += `{% trans "Available" %}: ${part.unallocated_stock}/${part.in_stock}`;
} else {
// Stock is completely available
- return `{% trans "Available" %}: ${part.unallocated_stock} ${units}`;
+ text += `{% trans "Available" %}: ${part.unallocated_stock}`;
}
} else {
// There IS NO stock available for this part
-
- if (part.ordering) {
- // There is no stock, but stock is on order
- return `{% trans "On Order" %}: ${part.ordering} ${units}`;
- } else if (part.building) {
- // There is no stock, but stock is being built
- return `{% trans "Building" %}: ${part.building} ${units}`;
- } else {
- // There is no stock
- var unit_badge = '';
- if (units) {
- // show units next to [No Stock] badge
- unit_badge = `{% trans "Unit" %}: ${units} `;
- }
- return `${unit_badge}{% trans "No Stock" %}`;
- }
+ text += `{% trans "No Stock" %}`;
}
+ // Check for items on order
+ if (part.ordering) {
+ text += ` | {% trans "On Order" %}: ${part.ordering}`;
+ }
+
+ // Check for items beeing built
+ if (part.building) {
+ text += ` | {% trans "Building" %}: ${part.building}`;
+ }
+
+ // Check for demand from unallocated build orders
+ var required = null;
+ inventreeGet(`/api/part/${part.pk}/requirements/`, {}, {
+ async: false,
+ success: function(response) {
+ required = 0;
+ if (response.required_build_order_quantity) {
+ required = response.required_build_order_quantity;
+ }
+ }
+ });
+ if (required == null) {
+ console.error(`Error loading part requirements for part ${part.pk}`);
+ return;
+ }
+ var demand = required - part.allocated_to_build_orders;
+ if (demand) {
+ text += ` | {% trans "Demand" %}: ${demand}`;
+ }
+
+ // Determine badge color based on overall stock health
+ var stock_health = part.in_stock + part.building + part.ordering - part.minimum_stock - demand;
+ var bg_class = '';
+ if (stock_health < 0) {
+ // Unsatisfied demand and/or below minimum stock
+ bg_class = 'bg-danger';
+ } else if (stock_health == 0) {
+ // Demand and minimum stock matched exactly by available stock and incoming/building
+ bg_class = 'bg-warning';
+ } else {
+ // Surplus stock available or already incoming/building in sufficient quantities
+ bg_class = 'bg-success';
+ }
+
+ // show units next to stock badge
+ var unit_badge = '';
+ if (units && !options.no_units) {
+ unit_badge = `{% trans "Unit" %}: ${units} `;
+ }
+
+ // return badge html
+ return `${unit_badge}${text}`;
}