From b827f14bf6e654c995de263768738c4f277c5cd8 Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@gmail.com>
Date: Thu, 8 Sep 2022 13:44:53 +1000
Subject: [PATCH] Pack quantity improvements (#3661)

* Specify serializer label

* Add units to part grid view

* improve display of stock units in part table

* Add units display to stock on part page

* Display units in supplier part table

* Simplify stock quantity display in stock table
---
 InvenTree/InvenTree/fields.py                 |  5 +--
 InvenTree/company/serializers.py              |  2 +-
 InvenTree/part/templates/part/part_base.html  |  4 +--
 .../part/templates/part/stock_count.html      |  2 +-
 InvenTree/templates/js/translated/company.js  |  9 +++++
 InvenTree/templates/js/translated/part.js     | 33 ++++++++++++-------
 InvenTree/templates/js/translated/stock.js    | 12 +++----
 7 files changed, 39 insertions(+), 28 deletions(-)

diff --git a/InvenTree/InvenTree/fields.py b/InvenTree/InvenTree/fields.py
index d8eff33093..caf625f2b8 100644
--- a/InvenTree/InvenTree/fields.py
+++ b/InvenTree/InvenTree/fields.py
@@ -165,11 +165,8 @@ class RoundingDecimalField(models.DecimalField):
 
     def formfield(self, **kwargs):
         """Return a Field instance for this field."""
-        defaults = {
-            'form_class': RoundingDecimalFormField
-        }
 
-        defaults.update(kwargs)
+        kwargs['form_class'] = RoundingDecimalFormField
 
         return super().formfield(**kwargs)
 
diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py
index ae88131997..2f74501fd0 100644
--- a/InvenTree/company/serializers.py
+++ b/InvenTree/company/serializers.py
@@ -239,7 +239,7 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
 
     pretty_name = serializers.CharField(read_only=True)
 
-    pack_size = serializers.FloatField()
+    pack_size = serializers.FloatField(label=_('Pack Quantity'))
 
     def __init__(self, *args, **kwargs):
         """Initialize this serializer with extra detail fields as required"""
diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html
index a0ad88bed6..69e6ccfdf8 100644
--- a/InvenTree/part/templates/part/part_base.html
+++ b/InvenTree/part/templates/part/part_base.html
@@ -198,14 +198,14 @@
         <tr>
             <td><span class='fas fa-flag'></span></td>
             <td>{% trans "Minimum Stock" %}</td>
-            <td>{{ part.minimum_stock }}</td>
+            <td>{{ part.minimum_stock }} {{ part.units }}</td>
         </tr>
         {% endif %}
         {% if on_order > 0 %}
         <tr>
             <td><span class='fas fa-shopping-cart'></span></td>
             <td>{% trans "On Order" %}</td>
-            <td>{% decimal on_order %}</td>
+            <td>{% decimal on_order %} {{ part.units }}</td>
         </tr>
         {% endif %}
         {% if part.component %}
diff --git a/InvenTree/part/templates/part/stock_count.html b/InvenTree/part/templates/part/stock_count.html
index 237c12b83e..517d502f9d 100644
--- a/InvenTree/part/templates/part/stock_count.html
+++ b/InvenTree/part/templates/part/stock_count.html
@@ -1,7 +1,7 @@
 {% load inventree_extras %}
 {% load i18n %}
 
-{% decimal total_stock %}
+{% decimal total_stock %} {{ part.units }}
 
 {% if total_stock == 0 %}
 <span class='badge badge-right rounded-pill bg-danger'>{% trans "No Stock" %}</span>
diff --git a/InvenTree/templates/js/translated/company.js b/InvenTree/templates/js/translated/company.js
index 6b90a78c87..4025e8b82c 100644
--- a/InvenTree/templates/js/translated/company.js
+++ b/InvenTree/templates/js/translated/company.js
@@ -995,6 +995,15 @@ function loadSupplierPartTable(table, url, options) {
                 field: 'pack_size',
                 title: '{% trans "Pack Quantity" %}',
                 sortable: true,
+                formatter: function(value, row) {
+                    var output = `${value}`;
+
+                    if (row.part_detail) {
+                        output += ` ${row.part_detail.units}`;
+                    }
+
+                    return output;
+                }
             },
             {
                 field: 'link',
diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js
index bc0e8c81db..46e9c7dba1 100644
--- a/InvenTree/templates/js/translated/part.js
+++ b/InvenTree/templates/js/translated/part.js
@@ -1324,22 +1324,23 @@ function partGridTile(part) {
     // Rows for table view
     var rows = '';
 
-    var stock = `${part.in_stock}`;
+    var units = part.units;
+    var stock = `${part.in_stock} ${part.units}`;
 
     if (!part.in_stock) {
         stock = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock" %}</span>`;
     } else if (!part.unallocated_stock) {
-        stock = `<span class='badge rounded-pill bg-warning'>{% trans "Not available" %}</span>`;
+        stock = `<span class='badge rounded-pill bg-warning'>{% trans "No Stock" %}</span>`;
     }
 
     rows += `<tr><td><b>{% trans "Stock" %}</b></td><td>${stock}</td></tr>`;
 
     if (part.ordering) {
-        rows += `<tr><td><b>{% trans "On Order" %}</b></td><td>${part.ordering}</td></tr>`;
+        rows += `<tr><td><b>{% trans "On Order" %}</b></td><td>${part.ordering} ${units}</td></tr>`;
     }
 
     if (part.building) {
-        rows += `<tr><td><b>{% trans "Building" %}</b></td><td>${part.building}</td></tr>`;
+        rows += `<tr><td><b>{% trans "Building" %}</b></td><td>${part.building} ${units}</td></tr>`;
     }
 
     var html = `
@@ -1356,10 +1357,10 @@ function partGridTile(part) {
             </div>
             <div class='panel-content'>
                 <div class='row'>
-                    <div class='col-sm-6'>
-                        <img src='${part.thumbnail}' class='card-thumb' onclick='showModalImage("${part.image}")'>
+                    <div class='col-sm-4'>
+                        <img src='${part.thumbnail}' style='width: 100%;' class='card-thumb' onclick='showModalImage("${part.image}")'>
                     </div>
-                    <div class='col-sm-6'>
+                    <div class='col-sm-8'>
                         <table class='table table-striped table-condensed'>
                             ${rows}
                         </table>
@@ -1505,11 +1506,7 @@ function loadPartTable(table, url, options={}) {
                 total_stock += row.variant_stock;
             }
 
-            if (row.unallocated_stock != row.in_stock) {
-                text = `${row.unallocated_stock} / ${total_stock}`;
-            } else {
-                text = `${total_stock}`;
-            }
+            var text = `${total_stock}`;
 
             // Construct extra informational badges
             var badges = '';
@@ -1538,6 +1535,18 @@ function loadPartTable(table, url, options={}) {
                 badges += `<span class='fas fa-info-circle float-right' title='{% trans "Includes variant stock" %}'></span>`;
             }
 
+            if (row.allocated_to_build_orders > 0) {
+                badges += `<span class='fas fa-bookmark icon-yellow float-right' title='{% trans "Allocated to build orders" %}: ${row.allocated_to_build_orders}'></span>`;
+            }
+
+            if (row.allocated_to_sales_orders > 0) {
+                badges += `<span class='fas fa-bookmark icon-yellow float-right' title='{% trans "Allocated to sales orders" %}: ${row.allocated_to_sales_orders}'></span>`;
+            }
+
+            if (row.units) {
+                text += ` <small>${row.units}</small>`;
+            }
+
             text = renderLink(text, `/part/${row.pk}/?display=part-stock`);
             text += badges;
 
diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js
index 877644f91e..67dd18fb41 100644
--- a/InvenTree/templates/js/translated/stock.js
+++ b/InvenTree/templates/js/translated/stock.js
@@ -1757,20 +1757,16 @@ function loadStockTable(table, options) {
 
             var val = '';
 
-            var available = Math.max(0, (row.quantity || 0) - (row.allocated || 0));
-
             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 = formatDecimal(available);
-                var tot = formatDecimal(row.quantity);
-
-                val = `${ava} / ${tot}`;
             } else {
                 // Format floating point numbers with this one weird trick
                 val = formatDecimal(value);
+
+                if (row.part_detail) {
+                    val += ` ${row.part_detail.units}`;
+                }
             }
 
             var html = renderLink(val, `/stock/item/${row.pk}/`);