mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 20:16:44 +00:00
Improved unit testing for BomItem
- tests for allowing variant parts - tests for allowing substitutes
This commit is contained in:
parent
0f8c279aa2
commit
f3074e8f34
@ -2337,18 +2337,29 @@ class BomItem(models.Model):
|
|||||||
"""
|
"""
|
||||||
Return a queryset filter for selecting StockItems which match this BomItem
|
Return a queryset filter for selecting StockItems which match this BomItem
|
||||||
|
|
||||||
|
- Allow stock from all directly specified substitute parts
|
||||||
- If allow_variants is True, allow all part variants
|
- If allow_variants is True, allow all part variants
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Target part
|
# List of parts we allow
|
||||||
part = self.sub_part
|
part_ids = set()
|
||||||
|
|
||||||
|
part_ids.add(self.sub_part.pk)
|
||||||
|
|
||||||
|
# Variant parts (if allowed)
|
||||||
if self.allow_variants:
|
if self.allow_variants:
|
||||||
variants = part.get_descendants(include_self=True)
|
variants = self.sub_part.get_descendants(include_self=False)
|
||||||
return Q(part__in=[v.pk for v in variants])
|
|
||||||
else:
|
for v in variants:
|
||||||
return Q(part=part)
|
part_ids.add(v.pk)
|
||||||
|
|
||||||
|
# Direct substitute parts
|
||||||
|
for sub in self.substitutes.all():
|
||||||
|
part_ids.add(sub.part.pk)
|
||||||
|
|
||||||
|
# Return a list of Part ID values which can be filtered against
|
||||||
|
return Q(part__in=[pk for pk in part_ids])
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import PIL
|
import PIL
|
||||||
|
|
||||||
@ -11,7 +12,8 @@ from InvenTree.api_tester import InvenTreeAPITestCase
|
|||||||
from InvenTree.status_codes import StockStatus
|
from InvenTree.status_codes import StockStatus
|
||||||
|
|
||||||
from part.models import Part, PartCategory
|
from part.models import Part, PartCategory
|
||||||
from stock.models import StockItem
|
from part.models import BomItem, BomItemSubstitute
|
||||||
|
from stock.models import StockItem, StockLocation
|
||||||
from company.models import Company
|
from company.models import Company
|
||||||
from common.models import InvenTreeSetting
|
from common.models import InvenTreeSetting
|
||||||
|
|
||||||
@ -273,53 +275,6 @@ class PartAPITest(InvenTreeAPITestCase):
|
|||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(len(response.data), 3)
|
self.assertEqual(len(response.data), 3)
|
||||||
|
|
||||||
def test_get_bom_list(self):
|
|
||||||
""" There should be 4 BomItem objects in the database """
|
|
||||||
url = reverse('api-bom-list')
|
|
||||||
response = self.client.get(url, format='json')
|
|
||||||
self.assertEqual(len(response.data), 5)
|
|
||||||
|
|
||||||
def test_get_bom_detail(self):
|
|
||||||
# Get the detail for a single BomItem
|
|
||||||
url = reverse('api-bom-item-detail', kwargs={'pk': 3})
|
|
||||||
response = self.client.get(url, format='json')
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertEqual(int(float(response.data['quantity'])), 25)
|
|
||||||
|
|
||||||
# Increase the quantity
|
|
||||||
data = response.data
|
|
||||||
data['quantity'] = 57
|
|
||||||
data['note'] = 'Added a note'
|
|
||||||
|
|
||||||
response = self.client.patch(url, data, format='json')
|
|
||||||
|
|
||||||
# Check that the quantity was increased and a note added
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertEqual(int(float(response.data['quantity'])), 57)
|
|
||||||
self.assertEqual(response.data['note'], 'Added a note')
|
|
||||||
|
|
||||||
def test_add_bom_item(self):
|
|
||||||
url = reverse('api-bom-list')
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'part': 100,
|
|
||||||
'sub_part': 4,
|
|
||||||
'quantity': 777,
|
|
||||||
}
|
|
||||||
|
|
||||||
response = self.client.post(url, data, format='json')
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
# Now try to create a BomItem which points to a non-assembly part (should fail)
|
|
||||||
data['part'] = 3
|
|
||||||
response = self.client.post(url, data, format='json')
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
# TODO - Now try to create a BomItem which references itself
|
|
||||||
data['part'] = 2
|
|
||||||
data['sub_part'] = 2
|
|
||||||
response = self.client.post(url, data, format='json')
|
|
||||||
|
|
||||||
def test_test_templates(self):
|
def test_test_templates(self):
|
||||||
|
|
||||||
url = reverse('api-part-test-template-list')
|
url = reverse('api-part-test-template-list')
|
||||||
@ -926,6 +881,249 @@ class PartAPIAggregationTest(InvenTreeAPITestCase):
|
|||||||
self.assertEqual(data['stock_item_count'], 105)
|
self.assertEqual(data['stock_item_count'], 105)
|
||||||
|
|
||||||
|
|
||||||
|
class BomItemTest(InvenTreeAPITestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for the BomItem API
|
||||||
|
"""
|
||||||
|
|
||||||
|
fixtures = [
|
||||||
|
'category',
|
||||||
|
'part',
|
||||||
|
'location',
|
||||||
|
'stock',
|
||||||
|
'bom',
|
||||||
|
'company',
|
||||||
|
]
|
||||||
|
|
||||||
|
roles = [
|
||||||
|
'part.add',
|
||||||
|
'part.change',
|
||||||
|
'part.delete',
|
||||||
|
]
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
def test_bom_list(self):
|
||||||
|
"""
|
||||||
|
Tests for the BomItem list endpoint
|
||||||
|
"""
|
||||||
|
|
||||||
|
# How many BOM items currently exist in the database?
|
||||||
|
n = BomItem.objects.count()
|
||||||
|
|
||||||
|
url = reverse('api-bom-list')
|
||||||
|
response = self.get(url, expected_code=200)
|
||||||
|
self.assertEqual(len(response.data), n)
|
||||||
|
|
||||||
|
# Now, filter by part
|
||||||
|
response = self.get(
|
||||||
|
url,
|
||||||
|
data={
|
||||||
|
'part': 100,
|
||||||
|
},
|
||||||
|
expected_code=200
|
||||||
|
)
|
||||||
|
|
||||||
|
print("results:", len(response.data))
|
||||||
|
|
||||||
|
def test_get_bom_detail(self):
|
||||||
|
"""
|
||||||
|
Get the detail view for a single BomItem object
|
||||||
|
"""
|
||||||
|
|
||||||
|
url = reverse('api-bom-item-detail', kwargs={'pk': 3})
|
||||||
|
|
||||||
|
response = self.get(url, expected_code=200)
|
||||||
|
|
||||||
|
self.assertEqual(int(float(response.data['quantity'])), 25)
|
||||||
|
|
||||||
|
# Increase the quantity
|
||||||
|
data = response.data
|
||||||
|
data['quantity'] = 57
|
||||||
|
data['note'] = 'Added a note'
|
||||||
|
|
||||||
|
response = self.patch(url, data, expected_code=200)
|
||||||
|
|
||||||
|
self.assertEqual(int(float(response.data['quantity'])), 57)
|
||||||
|
self.assertEqual(response.data['note'], 'Added a note')
|
||||||
|
|
||||||
|
def test_add_bom_item(self):
|
||||||
|
"""
|
||||||
|
Test that we can create a new BomItem via the API
|
||||||
|
"""
|
||||||
|
|
||||||
|
url = reverse('api-bom-list')
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'part': 100,
|
||||||
|
'sub_part': 4,
|
||||||
|
'quantity': 777,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.post(url, data, expected_code=201)
|
||||||
|
|
||||||
|
# Now try to create a BomItem which references itself
|
||||||
|
data['part'] = 100
|
||||||
|
data['sub_part'] = 100
|
||||||
|
self.client.post(url, data, expected_code=400)
|
||||||
|
|
||||||
|
def test_variants(self):
|
||||||
|
"""
|
||||||
|
Tests for BomItem use with variants
|
||||||
|
"""
|
||||||
|
|
||||||
|
stock_url = reverse('api-stock-list')
|
||||||
|
|
||||||
|
# BOM item we are interested in
|
||||||
|
bom_item = BomItem.objects.get(pk=1)
|
||||||
|
|
||||||
|
bom_item.allow_variants = True
|
||||||
|
bom_item.save()
|
||||||
|
|
||||||
|
# sub part that the BOM item points to
|
||||||
|
sub_part = bom_item.sub_part
|
||||||
|
|
||||||
|
sub_part.is_template = True
|
||||||
|
sub_part.save()
|
||||||
|
|
||||||
|
# How many stock items are initially available for this part?
|
||||||
|
response = self.get(
|
||||||
|
stock_url,
|
||||||
|
{
|
||||||
|
'bom_item': bom_item.pk,
|
||||||
|
},
|
||||||
|
expected_code=200
|
||||||
|
)
|
||||||
|
|
||||||
|
n_items = len(response.data)
|
||||||
|
self.assertEqual(n_items, 2)
|
||||||
|
|
||||||
|
loc = StockLocation.objects.get(pk=1)
|
||||||
|
|
||||||
|
# Now we will create some variant parts and stock
|
||||||
|
for ii in range(5):
|
||||||
|
|
||||||
|
# Create a variant part!
|
||||||
|
variant = Part.objects.create(
|
||||||
|
name=f"Variant_{ii}",
|
||||||
|
description="A variant part",
|
||||||
|
component=True,
|
||||||
|
variant_of=sub_part
|
||||||
|
)
|
||||||
|
|
||||||
|
variant.save()
|
||||||
|
|
||||||
|
Part.objects.rebuild()
|
||||||
|
|
||||||
|
# Create some stock items for this new part
|
||||||
|
for jj in range(ii):
|
||||||
|
StockItem.objects.create(
|
||||||
|
part=variant,
|
||||||
|
location=loc,
|
||||||
|
quantity=100
|
||||||
|
)
|
||||||
|
|
||||||
|
# Keep track of running total
|
||||||
|
n_items += jj
|
||||||
|
|
||||||
|
# Now, there should be more stock items available!
|
||||||
|
response = self.get(
|
||||||
|
stock_url,
|
||||||
|
{
|
||||||
|
'bom_item': bom_item.pk,
|
||||||
|
},
|
||||||
|
expected_code=200
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(response.data), n_items)
|
||||||
|
|
||||||
|
# Now, disallow variant parts in the BomItem
|
||||||
|
bom_item.allow_variants = False
|
||||||
|
bom_item.save()
|
||||||
|
|
||||||
|
# There should now only be 2 stock items available again
|
||||||
|
response = self.get(
|
||||||
|
stock_url,
|
||||||
|
{
|
||||||
|
'bom_item': bom_item.pk,
|
||||||
|
},
|
||||||
|
expected_code=200
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(response.data), 2)
|
||||||
|
|
||||||
|
def test_substitutes(self):
|
||||||
|
"""
|
||||||
|
Tests for BomItem substitutes
|
||||||
|
"""
|
||||||
|
|
||||||
|
url = reverse('api-bom-substitute-list')
|
||||||
|
stock_url = reverse('api-stock-list')
|
||||||
|
|
||||||
|
# Initially we have no substitute parts
|
||||||
|
response = self.get(url, expected_code=200)
|
||||||
|
self.assertEqual(len(response.data), 0)
|
||||||
|
|
||||||
|
# BOM item we are interested in
|
||||||
|
bom_item = BomItem.objects.get(pk=1)
|
||||||
|
|
||||||
|
# Filter stock items which can be assigned against this stock item
|
||||||
|
response = self.get(
|
||||||
|
stock_url,
|
||||||
|
{
|
||||||
|
"bom_item": bom_item.pk,
|
||||||
|
},
|
||||||
|
expected_code=200
|
||||||
|
)
|
||||||
|
|
||||||
|
n_items = len(response.data)
|
||||||
|
|
||||||
|
loc = StockLocation.objects.get(pk=1)
|
||||||
|
|
||||||
|
# Let's make some!
|
||||||
|
for ii in range(5):
|
||||||
|
sub_part = Part.objects.create(
|
||||||
|
name=f"Substitute {ii}",
|
||||||
|
description="A substitute part",
|
||||||
|
component=True,
|
||||||
|
is_template=False,
|
||||||
|
assembly=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a new StockItem for this Part
|
||||||
|
StockItem.objects.create(
|
||||||
|
part=sub_part,
|
||||||
|
quantity=1000,
|
||||||
|
location=loc,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now, create an "alternative" for the BOM Item
|
||||||
|
BomItemSubstitute.objects.create(
|
||||||
|
bom_item=bom_item,
|
||||||
|
part=sub_part
|
||||||
|
)
|
||||||
|
|
||||||
|
# We should be able to filter the API list to just return this new part
|
||||||
|
response = self.get(url, data={'part': sub_part.pk}, expected_code=200)
|
||||||
|
self.assertEqual(len(response.data), 1)
|
||||||
|
|
||||||
|
# We should also have more stock available to allocate against this BOM item!
|
||||||
|
response = self.get(
|
||||||
|
stock_url,
|
||||||
|
{
|
||||||
|
"bom_item": bom_item.pk,
|
||||||
|
},
|
||||||
|
expected_code=200
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(response.data), n_items + ii + 1)
|
||||||
|
|
||||||
|
# There should now be 5 substitute parts available in the database
|
||||||
|
response = self.get(url, expected_code=200)
|
||||||
|
self.assertEqual(len(response.data), 5)
|
||||||
|
|
||||||
|
|
||||||
class PartParameterTest(InvenTreeAPITestCase):
|
class PartParameterTest(InvenTreeAPITestCase):
|
||||||
"""
|
"""
|
||||||
Tests for the ParParameter API
|
Tests for the ParParameter API
|
||||||
|
Loading…
x
Reference in New Issue
Block a user