mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-16 03:55:41 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
@ -7,6 +7,9 @@ INVENTREE_API_VERSION = 61
|
||||
"""
|
||||
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
||||
|
||||
v62 -> 2022-07-05 : https://github.com/inventree/InvenTree/pull/3296
|
||||
- Allows search on BOM List API endpoint
|
||||
|
||||
v61 -> 2022-06-12 : https://github.com/inventree/InvenTree/pull/3183
|
||||
- Migrate the "Convert Stock Item" form class to use the API
|
||||
- There is now an API endpoint for converting a stock item to a valid variant
|
||||
|
@ -741,7 +741,7 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
|
||||
"""Initialization routine for the serializer"""
|
||||
order_detail = kwargs.pop('order_detail', False)
|
||||
part_detail = kwargs.pop('part_detail', True)
|
||||
item_detail = kwargs.pop('item_detail', False)
|
||||
item_detail = kwargs.pop('item_detail', True)
|
||||
location_detail = kwargs.pop('location_detail', False)
|
||||
customer_detail = kwargs.pop('customer_detail', False)
|
||||
|
||||
|
@ -24,6 +24,7 @@ from common.models import InvenTreeSetting
|
||||
from company.models import Company, ManufacturerPart, SupplierPart
|
||||
from InvenTree.api import (APIDownloadMixin, AttachmentMixin,
|
||||
ListCreateDestroyAPIView)
|
||||
from InvenTree.filters import InvenTreeOrderingFilter
|
||||
from InvenTree.helpers import DownloadFile, increment, isNull, str2bool
|
||||
from InvenTree.mixins import (CreateAPI, ListAPI, ListCreateAPI, RetrieveAPI,
|
||||
RetrieveUpdateAPI, RetrieveUpdateDestroyAPI,
|
||||
@ -1756,12 +1757,32 @@ class BomList(ListCreateDestroyAPIView):
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
filters.OrderingFilter,
|
||||
InvenTreeOrderingFilter,
|
||||
]
|
||||
|
||||
filterset_fields = [
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'reference',
|
||||
'sub_part__name',
|
||||
'sub_part__description',
|
||||
'sub_part__IPN',
|
||||
'sub_part__revision',
|
||||
'sub_part__keywords',
|
||||
'sub_part__category__name',
|
||||
]
|
||||
|
||||
ordering_fields = [
|
||||
'quantity',
|
||||
'sub_part',
|
||||
'available_stock',
|
||||
]
|
||||
|
||||
ordering_field_aliases = {
|
||||
'sub_part': 'sub_part__name',
|
||||
}
|
||||
|
||||
|
||||
class BomImportUpload(CreateAPI):
|
||||
"""API endpoint for uploading a complete Bill of Materials.
|
||||
|
@ -24,6 +24,7 @@
|
||||
part: 100
|
||||
sub_part: 5
|
||||
quantity: 25
|
||||
reference: ABCDE
|
||||
|
||||
# 3 x Orphan
|
||||
- model: part.bomitem
|
||||
@ -32,6 +33,7 @@
|
||||
part: 100
|
||||
sub_part: 50
|
||||
quantity: 3
|
||||
reference: VWXYZ
|
||||
|
||||
- model: part.bomitem
|
||||
pk: 5
|
||||
@ -39,6 +41,7 @@
|
||||
part: 1
|
||||
sub_part: 5
|
||||
quantity: 3
|
||||
reference: LMNOP
|
||||
|
||||
# Make "Assembly" from "Bob"
|
||||
- model: part.bomitem
|
||||
|
@ -1647,6 +1647,102 @@ class BomItemTest(InvenTreeAPITestCase):
|
||||
for key in ['available_stock', 'available_substitute_stock']:
|
||||
self.assertTrue(key in el)
|
||||
|
||||
def test_bom_list_search(self):
|
||||
"""Test that we can search the BOM list API endpoint"""
|
||||
|
||||
url = reverse('api-bom-list')
|
||||
|
||||
response = self.get(url, expected_code=200)
|
||||
|
||||
self.assertEqual(len(response.data), 6)
|
||||
|
||||
# Limit the results with a search term
|
||||
response = self.get(
|
||||
url,
|
||||
{
|
||||
'search': '0805',
|
||||
},
|
||||
expected_code=200,
|
||||
)
|
||||
|
||||
self.assertEqual(len(response.data), 3)
|
||||
|
||||
# Search by 'reference' field
|
||||
for q in ['ABCDE', 'LMNOP', 'VWXYZ']:
|
||||
response = self.get(
|
||||
url,
|
||||
{
|
||||
'search': q,
|
||||
},
|
||||
expected_code=200
|
||||
)
|
||||
|
||||
self.assertEqual(len(response.data), 1)
|
||||
self.assertEqual(response.data[0]['reference'], q)
|
||||
|
||||
# Search by nonsense data
|
||||
response = self.get(
|
||||
url,
|
||||
{
|
||||
'search': 'xxxxxxxxxxxxxxxxx',
|
||||
},
|
||||
expected_code=200
|
||||
)
|
||||
|
||||
self.assertEqual(len(response.data), 0)
|
||||
|
||||
def test_bom_list_ordering(self):
|
||||
"""Test that the BOM list results can be ordered"""
|
||||
|
||||
url = reverse('api-bom-list')
|
||||
|
||||
# Order by increasing quantity
|
||||
response = self.get(
|
||||
f"{url}?ordering=+quantity",
|
||||
expected_code=200
|
||||
)
|
||||
|
||||
self.assertEqual(len(response.data), 6)
|
||||
|
||||
q1 = response.data[0]['quantity']
|
||||
q2 = response.data[-1]['quantity']
|
||||
|
||||
self.assertTrue(q1 < q2)
|
||||
|
||||
# Order by decreasing quantity
|
||||
response = self.get(
|
||||
f"{url}?ordering=-quantity",
|
||||
expected_code=200,
|
||||
)
|
||||
|
||||
self.assertEqual(q1, response.data[-1]['quantity'])
|
||||
self.assertEqual(q2, response.data[0]['quantity'])
|
||||
|
||||
# Now test ordering by 'sub_part' (which is actually 'sub_part__name')
|
||||
response = self.get(
|
||||
url,
|
||||
{
|
||||
'ordering': 'sub_part',
|
||||
'sub_part_detail': True,
|
||||
},
|
||||
expected_code=200,
|
||||
)
|
||||
|
||||
n1 = response.data[0]['sub_part_detail']['name']
|
||||
n2 = response.data[-1]['sub_part_detail']['name']
|
||||
|
||||
response = self.get(
|
||||
url,
|
||||
{
|
||||
'ordering': '-sub_part',
|
||||
'sub_part_detail': True,
|
||||
},
|
||||
expected_code=200,
|
||||
)
|
||||
|
||||
self.assertEqual(n1, response.data[-1]['sub_part_detail']['name'])
|
||||
self.assertEqual(n2, response.data[0]['sub_part_detail']['name'])
|
||||
|
||||
def test_get_bom_detail(self):
|
||||
"""Get the detail view for a single BomItem object."""
|
||||
url = reverse('api-bom-item-detail', kwargs={'pk': 3})
|
||||
|
@ -3236,10 +3236,12 @@ function showAllocationSubTable(index, row, element, options) {
|
||||
formatter: function(value, row, index, field) {
|
||||
var text = '';
|
||||
|
||||
if (row.serial != null && row.quantity == 1) {
|
||||
text = `{% trans "Serial Number" %}: ${row.serial}`;
|
||||
} else {
|
||||
text = `{% trans "Quantity" %}: ${row.quantity}`;
|
||||
var item = row.item_detail;
|
||||
|
||||
var text = `{% trans "Quantity" %}: ${row.quantity}`;
|
||||
|
||||
if (item && item.serial != null && row.quantity == 1) {
|
||||
text = `{% trans "Serial Number" %}: ${item.serial}`;
|
||||
}
|
||||
|
||||
return renderLink(text, `/stock/item/${row.item}/`);
|
||||
|
@ -94,9 +94,9 @@ distlib==0.3.4 \
|
||||
--hash=sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b \
|
||||
--hash=sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579
|
||||
# via virtualenv
|
||||
django==3.2.13 \
|
||||
--hash=sha256:6d93497a0a9bf6ba0e0b1a29cccdc40efbfc76297255b1309b3a884a688ec4b6 \
|
||||
--hash=sha256:b896ca61edc079eb6bbaa15cf6071eb69d6aac08cce5211583cfb41515644fdf
|
||||
django==3.2.14 \
|
||||
--hash=sha256:677182ba8b5b285a4e072f3ac17ceee6aff1b5ce77fd173cc5b6a2d3dc022fcf \
|
||||
--hash=sha256:a8681e098fa60f7c33a4b628d6fcd3fe983a0939ff1301ecacac21d0b38bad56
|
||||
# via
|
||||
# -c requirements.txt
|
||||
# django-debug-toolbar
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Please keep this list sorted - if you pin a version provide a reason
|
||||
Django<4 # Django package
|
||||
Django>=3.2.14,<4 # Django package
|
||||
coreapi # API documentation for djangorestframework
|
||||
cryptography==3.4.8 # Core cryptographic functionality
|
||||
django-allauth # SSO for external providers via OpenID
|
||||
|
@ -42,7 +42,7 @@ defusedxml==0.7.1
|
||||
# python3-openid
|
||||
diff-match-patch==20200713
|
||||
# via django-import-export
|
||||
django==3.2.13
|
||||
django==3.2.14
|
||||
# via
|
||||
# -r requirements.in
|
||||
# django-allauth
|
||||
|
Reference in New Issue
Block a user