Fix 'get_required_quantity' method (#13002)

- Ensure a Decimal is returned in all cases
- Fix type hinting
- Updated unit tests
This commit is contained in:
Oliver authored and GitHub committed 2026-10-13 22:37:09 +11:00
1 parent 788b291a21
commit c3917c343b
2 files changed
+25 -4

No files matched your search

+7 -4
View File
@@ -4316,11 +4316,11 @@ class BomItem(InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel):
return int(Decimal(available_stock) / n)
def get_required_quantity(self, build_quantity: float) -> float:
def get_required_quantity(self, build_quantity: Decimal | float | int) -> Decimal:
"""Calculate the required part quantity, based on the supplied build_quantity.
Arguments:
build_quantity: Number of assemblies to build
build_quantity: Number of assemblies to build (converted to Decimal)
Returns:
Production quantity required for this component
@@ -4330,6 +4330,10 @@ class BomItem(InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel):
and piece_count indicates how many pieces are needed.
Total material = quantity x piece_count x build_quantity.
"""
# Convert via str to avoid binary float artefacts (e.g. 0.1 -> 0.1000000000000000055...)
if not isinstance(build_quantity, Decimal):
build_quantity = Decimal(str(build_quantity))
# Base quantity requirement (quantity is per-piece, piece_count is number of pieces)
required = self.quantity * self.piece_count * build_quantity
@@ -4356,8 +4360,7 @@ class BomItem(InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel):
try:
round_up = Decimal(self.rounding_multiple)
value = Decimal(required)
value = math.ceil(value / round_up) * round_up
required = float(value)
required = math.ceil(value / round_up) * round_up
except InvalidOperation:
log_error('bom_item.get_required_quantity')
@@ -1,6 +1,7 @@
"""Unit tests for the BomItem model."""
import hashlib
from decimal import Decimal
from unittest import mock
import django.core.exceptions as django_exceptions
@@ -112,6 +113,20 @@ class BomItemTest(TestCase):
self.assertEqual(item.get_required_quantity(1), 8) # 3 + 5 = 8
self.assertEqual(item.get_required_quantity(10), 35) # 30 + 5 = 35
def test_required_quantity_input_types(self):
"""Test that get_required_quantity accepts int, float and Decimal inputs."""
item = BomItem.objects.get(pk=4)
for value, expected in [
(10, Decimal('30')),
(Decimal('2.5'), Decimal('7.5')),
(2.5, Decimal('7.5')),
(0.1, Decimal('0.3')),
]:
result = item.get_required_quantity(value)
self.assertIsInstance(result, Decimal)
self.assertEqual(result, expected)
def test_round_up(self):
"""Test the 'rounding_multiple' attribute."""
item = BomItem.objects.get(pk=4)
@@ -143,6 +158,9 @@ class BomItemTest(TestCase):
item.get_required_quantity(100), 306
) # 3 x 100 = 300, rounded up to nearest multiple of 17
# The rounded value must remain a Decimal (not a float)
self.assertIsInstance(item.get_required_quantity(10), Decimal)
# Next, let's create a new Build order
bo = build.models.Build.objects.create(
part=item.part, quantity=21, reference='BO-9999', title='Test Build Order'