mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-15 11:35:41 +00:00
Rename "pack_units" to "pack_quantity"
This commit is contained in:
@ -8,7 +8,7 @@ INVENTREE_API_VERSION = 117
|
|||||||
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
||||||
|
|
||||||
v117 -> 2023-05-22 : https://github.com/inventree/InvenTree/pull/4854
|
v117 -> 2023-05-22 : https://github.com/inventree/InvenTree/pull/4854
|
||||||
- Replaces SupplierPart "pack_size" field with "pack_units"
|
- Replaces SupplierPart "pack_size" field with "pack_quantity"
|
||||||
- New field supports physical units, and allows for conversion between compatible units
|
- New field supports physical units, and allows for conversion between compatible units
|
||||||
|
|
||||||
v116 -> 2023-05-18 : https://github.com/inventree/InvenTree/pull/4823
|
v116 -> 2023-05-18 : https://github.com/inventree/InvenTree/pull/4823
|
||||||
|
@ -390,7 +390,7 @@ class SupplierPartList(ListCreateDestroyAPIView):
|
|||||||
'manufacturer',
|
'manufacturer',
|
||||||
'MPN',
|
'MPN',
|
||||||
'packaging',
|
'packaging',
|
||||||
'pack_units',
|
'pack_quantity',
|
||||||
'in_stock',
|
'in_stock',
|
||||||
'updated',
|
'updated',
|
||||||
]
|
]
|
||||||
@ -400,7 +400,7 @@ class SupplierPartList(ListCreateDestroyAPIView):
|
|||||||
'supplier': 'supplier__name',
|
'supplier': 'supplier__name',
|
||||||
'manufacturer': 'manufacturer_part__manufacturer__name',
|
'manufacturer': 'manufacturer_part__manufacturer__name',
|
||||||
'MPN': 'manufacturer_part__MPN',
|
'MPN': 'manufacturer_part__MPN',
|
||||||
'pack_units': ['pack_units_native', 'pack_units'],
|
'pack_quantity': ['pack_quantity_native', 'pack_quantity'],
|
||||||
}
|
}
|
||||||
|
|
||||||
search_fields = [
|
search_fields = [
|
||||||
|
@ -66,4 +66,4 @@
|
|||||||
part: 4
|
part: 4
|
||||||
supplier: 2
|
supplier: 2
|
||||||
SKU: 'R_4K7_0603.100PCK'
|
SKU: 'R_4K7_0603.100PCK'
|
||||||
pack_units: '100'
|
pack_quantity: '100'
|
||||||
|
@ -14,12 +14,12 @@ class Migration(migrations.Migration):
|
|||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='supplierpart',
|
model_name='supplierpart',
|
||||||
name='pack_units',
|
name='pack_quantity',
|
||||||
field=models.CharField(blank=True, help_text='Units of measure for this supplier part', max_length=25, verbose_name='Packaging Units'),
|
field=models.CharField(blank=True, help_text='Total quantity supplied in a single pack. Leave empty for single items.', max_length=25, verbose_name='Pack Quantity'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='supplierpart',
|
model_name='supplierpart',
|
||||||
name='pack_units_native',
|
name='pack_quantity_native',
|
||||||
field=InvenTree.fields.RoundingDecimalField(decimal_places=10, default=1, max_digits=20, null=True),
|
field=InvenTree.fields.RoundingDecimalField(decimal_places=10, default=1, max_digits=20, null=True),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -14,8 +14,8 @@ def update_supplier_part_units(apps, schema_editor):
|
|||||||
|
|
||||||
for sp in supplier_parts:
|
for sp in supplier_parts:
|
||||||
pack_size = normalize(sp.pack_size)
|
pack_size = normalize(sp.pack_size)
|
||||||
sp.pack_units = str(pack_size)
|
sp.pack_quantity = str(pack_size)
|
||||||
sp.pack_units_native = pack_size
|
sp.pack_quantity_native = pack_size
|
||||||
sp.save()
|
sp.save()
|
||||||
|
|
||||||
if supplier_parts.count() > 0:
|
if supplier_parts.count() > 0:
|
||||||
|
@ -438,8 +438,8 @@ class SupplierPart(MetadataMixin, InvenTreeBarcodeMixin, common.models.MetaMixin
|
|||||||
multiple: Multiple that the part is provided in
|
multiple: Multiple that the part is provided in
|
||||||
lead_time: Supplier lead time
|
lead_time: Supplier lead time
|
||||||
packaging: packaging that the part is supplied in, e.g. "Reel"
|
packaging: packaging that the part is supplied in, e.g. "Reel"
|
||||||
pack_units: Quantity of item supplied in a single pack (e.g. 30ml in a single tube)
|
pack_quantity: Quantity of item supplied in a single pack (e.g. 30ml in a single tube)
|
||||||
pack_units_native: Pack units, converted to "native" units of the referenced part
|
pack_quantity_native: Pack quantity, converted to "native" units of the referenced part
|
||||||
updated: Date that the SupplierPart was last updated
|
updated: Date that the SupplierPart was last updated
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -478,38 +478,38 @@ class SupplierPart(MetadataMixin, InvenTreeBarcodeMixin, common.models.MetaMixin
|
|||||||
"""
|
"""
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
self.pack_units = self.pack_units.strip()
|
self.pack_quantity = self.pack_quantity.strip()
|
||||||
|
|
||||||
# An empty 'pack_units' value is equivalent to '1'
|
# An empty 'pack_quantity' value is equivalent to '1'
|
||||||
if self.pack_units == '':
|
if self.pack_quantity == '':
|
||||||
self.pack_units = '1'
|
self.pack_quantity = '1'
|
||||||
|
|
||||||
# Validate that the UOM is compatible with the base part
|
# Validate that the UOM is compatible with the base part
|
||||||
if self.pack_units and self.part:
|
if self.pack_quantity and self.part:
|
||||||
try:
|
try:
|
||||||
# Attempt conversion to specified unit
|
# Attempt conversion to specified unit
|
||||||
native_value = InvenTree.conversion.convert_physical_value(
|
native_value = InvenTree.conversion.convert_physical_value(
|
||||||
self.pack_units, self.part.units
|
self.pack_quantity, self.part.units
|
||||||
)
|
)
|
||||||
|
|
||||||
# If part units are not provided, value must be dimensionless
|
# If part units are not provided, value must be dimensionless
|
||||||
if not self.part.units and native_value.units not in ['', 'dimensionless']:
|
if not self.part.units and native_value.units not in ['', 'dimensionless']:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'pack_units': _("Pack units must be compatible with the base part units")
|
'pack_quantity': _("Pack units must be compatible with the base part units")
|
||||||
})
|
})
|
||||||
|
|
||||||
# Native value must be greater than zero
|
# Native value must be greater than zero
|
||||||
if float(native_value.magnitude) <= 0:
|
if float(native_value.magnitude) <= 0:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'pack_units': _("Pack units must be greater than zero")
|
'pack_quantity': _("Pack units must be greater than zero")
|
||||||
})
|
})
|
||||||
|
|
||||||
# Update native pack units value
|
# Update native pack units value
|
||||||
self.pack_units_native = Decimal(native_value.magnitude)
|
self.pack_quantity_native = Decimal(native_value.magnitude)
|
||||||
|
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'pack_units': e.messages
|
'pack_quantity': e.messages
|
||||||
})
|
})
|
||||||
|
|
||||||
# Ensure that the linked manufacturer_part points to the same part!
|
# Ensure that the linked manufacturer_part points to the same part!
|
||||||
@ -601,14 +601,14 @@ class SupplierPart(MetadataMixin, InvenTreeBarcodeMixin, common.models.MetaMixin
|
|||||||
|
|
||||||
packaging = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('Packaging'), help_text=_('Part packaging'))
|
packaging = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('Packaging'), help_text=_('Part packaging'))
|
||||||
|
|
||||||
pack_units = models.CharField(
|
pack_quantity = models.CharField(
|
||||||
max_length=25,
|
max_length=25,
|
||||||
verbose_name=_('Packaging Units'),
|
verbose_name=_('Pack Quantity'),
|
||||||
help_text=_('Units of measure for this supplier part'),
|
help_text=_('Total quantity supplied in a single pack. Leave empty for single items.'),
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
pack_units_native = RoundingDecimalField(
|
pack_quantity_native = RoundingDecimalField(
|
||||||
max_digits=20, decimal_places=10, default=1,
|
max_digits=20, decimal_places=10, default=1,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
@ -265,7 +265,8 @@ class SupplierPartSerializer(InvenTreeTagModelSerializer):
|
|||||||
'pk',
|
'pk',
|
||||||
'barcode_hash',
|
'barcode_hash',
|
||||||
'packaging',
|
'packaging',
|
||||||
'pack_units',
|
'pack_quantity',
|
||||||
|
'pack_quantity_native',
|
||||||
'part',
|
'part',
|
||||||
'part_detail',
|
'part_detail',
|
||||||
'pretty_name',
|
'pretty_name',
|
||||||
|
@ -162,7 +162,7 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<td>{{ part.packaging }}{% include "clip.html" %}</td>
|
<td>{{ part.packaging }}{% include "clip.html" %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if part.pack_units %}
|
{% if part.pack_quantity %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-box'></span></td>
|
<td><span class='fas fa-box'></span></td>
|
||||||
<td>
|
<td>
|
||||||
@ -174,10 +174,10 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ part.pack_units }}
|
{{ part.pack_quantity }}
|
||||||
{% include "clip.html" %}
|
{% include "clip.html" %}
|
||||||
{% if part.part.units and part.pack_units_native %}
|
{% if part.part.units and part.pack_quantity_native %}
|
||||||
<span class='fas fa-info-circle float-right' title='{% decimal part.pack_units_native %} {{ part.part.units }}'></span>
|
<span class='fas fa-info-circle float-right' title='{% decimal part.pack_quantity_native %} {{ part.part.units }}'></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -10,10 +10,10 @@ from part.models import Part
|
|||||||
|
|
||||||
|
|
||||||
class SupplierPartPackUnitsTests(InvenTreeTestCase):
|
class SupplierPartPackUnitsTests(InvenTreeTestCase):
|
||||||
"""Unit tests for the SupplierPart pack_units field"""
|
"""Unit tests for the SupplierPart pack_quantity field"""
|
||||||
|
|
||||||
def test_pack_units_dimensionless(self):
|
def test_pack_quantity_dimensionless(self):
|
||||||
"""Test valid values for the 'pack_units' field"""
|
"""Test valid values for the 'pack_quantity' field"""
|
||||||
|
|
||||||
# Create a part without units (dimensionless)
|
# Create a part without units (dimensionless)
|
||||||
part = Part.objects.create(name='Test Part', description='Test part description', component=True)
|
part = Part.objects.create(name='Test Part', description='Test part description', component=True)
|
||||||
@ -48,17 +48,17 @@ class SupplierPartPackUnitsTests(InvenTreeTestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
for test, expected in pass_tests.items():
|
for test, expected in pass_tests.items():
|
||||||
sp.pack_units = test
|
sp.pack_quantity = test
|
||||||
sp.full_clean()
|
sp.full_clean()
|
||||||
self.assertEqual(sp.pack_units_native, expected)
|
self.assertEqual(sp.pack_quantity_native, expected)
|
||||||
|
|
||||||
for test in fail_tests:
|
for test in fail_tests:
|
||||||
sp.pack_units = test
|
sp.pack_quantity = test
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
sp.full_clean()
|
sp.full_clean()
|
||||||
|
|
||||||
def test_pack_units(self):
|
def test_pack_quantity(self):
|
||||||
"""Test pack_units for a part with a specified dimension"""
|
"""Test pack_quantity for a part with a specified dimension"""
|
||||||
|
|
||||||
# Create a part with units 'm'
|
# Create a part with units 'm'
|
||||||
part = Part.objects.create(name='Test Part', description='Test part description', component=True, units='m')
|
part = Part.objects.create(name='Test Part', description='Test part description', component=True, units='m')
|
||||||
@ -101,14 +101,14 @@ class SupplierPartPackUnitsTests(InvenTreeTestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
for test, expected in pass_tests.items():
|
for test, expected in pass_tests.items():
|
||||||
sp.pack_units = test
|
sp.pack_quantity = test
|
||||||
sp.full_clean()
|
sp.full_clean()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
round(Decimal(sp.pack_units_native), 10),
|
round(Decimal(sp.pack_quantity_native), 10),
|
||||||
round(Decimal(str(expected)), 10)
|
round(Decimal(str(expected)), 10)
|
||||||
)
|
)
|
||||||
|
|
||||||
for test in fail_tests:
|
for test in fail_tests:
|
||||||
sp.pack_units = test
|
sp.pack_quantity = test
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
sp.full_clean()
|
sp.full_clean()
|
||||||
|
@ -656,7 +656,7 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Update the number of parts received against the particular line item
|
# Update the number of parts received against the particular line item
|
||||||
# Note that this quantity does *not* take the pack_size into account, it is "number of packs"
|
# Note that this quantity does *not* take the pack_quantity into account, it is "number of packs"
|
||||||
line.received += quantity
|
line.received += quantity
|
||||||
line.save()
|
line.save()
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ class OrderTest(TestCase):
|
|||||||
part=prt,
|
part=prt,
|
||||||
supplier=sup,
|
supplier=sup,
|
||||||
SKU='SKUx10',
|
SKU='SKUx10',
|
||||||
pack_units='10',
|
pack_quantity='10',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a new supplier part with smaller pack size
|
# Create a new supplier part with smaller pack size
|
||||||
@ -236,7 +236,7 @@ class OrderTest(TestCase):
|
|||||||
part=prt,
|
part=prt,
|
||||||
supplier=sup,
|
supplier=sup,
|
||||||
SKU='SKUx0.1',
|
SKU='SKUx0.1',
|
||||||
pack_units='0.1',
|
pack_quantity='0.1',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Record values before we start
|
# Record values before we start
|
||||||
|
@ -63,14 +63,14 @@ class PartPricingTests(InvenTreeTestCase):
|
|||||||
supplier=self.supplier_2,
|
supplier=self.supplier_2,
|
||||||
part=self.part,
|
part=self.part,
|
||||||
SKU='SUP_2',
|
SKU='SUP_2',
|
||||||
pack_units='2.5',
|
pack_quantity='2.5',
|
||||||
)
|
)
|
||||||
|
|
||||||
self.sp_3 = company.models.SupplierPart.objects.create(
|
self.sp_3 = company.models.SupplierPart.objects.create(
|
||||||
supplier=self.supplier_2,
|
supplier=self.supplier_2,
|
||||||
part=self.part,
|
part=self.part,
|
||||||
SKU='SUP_3',
|
SKU='SUP_3',
|
||||||
pack_units='10'
|
pack_quantity='10'
|
||||||
)
|
)
|
||||||
|
|
||||||
company.models.SupplierPriceBreak.objects.create(
|
company.models.SupplierPriceBreak.objects.create(
|
||||||
@ -322,7 +322,7 @@ class PartPricingTests(InvenTreeTestCase):
|
|||||||
# $5 AUD each
|
# $5 AUD each
|
||||||
line_1 = po.add_line_item(self.sp_2, quantity=10, purchase_price=Money(5, 'AUD'))
|
line_1 = po.add_line_item(self.sp_2, quantity=10, purchase_price=Money(5, 'AUD'))
|
||||||
|
|
||||||
# $30 CAD each (but pack_units is 10, so really $3 CAD each)
|
# $30 CAD each (but pack_size is 10, so really $3 CAD each)
|
||||||
line_2 = po.add_line_item(self.sp_3, quantity=5, purchase_price=Money(30, 'CAD'))
|
line_2 = po.add_line_item(self.sp_3, quantity=5, purchase_price=Money(30, 'CAD'))
|
||||||
|
|
||||||
pricing.update_purchase_cost()
|
pricing.update_purchase_cost()
|
||||||
|
@ -138,7 +138,7 @@ function supplierPartFields(options={}) {
|
|||||||
packaging: {
|
packaging: {
|
||||||
icon: 'fa-box',
|
icon: 'fa-box',
|
||||||
},
|
},
|
||||||
pack_units: {},
|
pack_quantity: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.part) {
|
if (options.part) {
|
||||||
@ -1242,8 +1242,8 @@ function loadSupplierPartTable(table, url, options) {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'pack_units',
|
field: 'pack_quantity',
|
||||||
title: '{% trans "Pack Units" %}',
|
title: '{% trans "Pack Quantity" %}',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
formatter: function(value, row) {
|
formatter: function(value, row) {
|
||||||
var output = `${value}`;
|
var output = `${value}`;
|
||||||
|
Reference in New Issue
Block a user