mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-17 04:25:42 +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
|
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
|
v61 -> 2022-06-12 : https://github.com/inventree/InvenTree/pull/3183
|
||||||
- Migrate the "Convert Stock Item" form class to use the API
|
- 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
|
- 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"""
|
"""Initialization routine for the serializer"""
|
||||||
order_detail = kwargs.pop('order_detail', False)
|
order_detail = kwargs.pop('order_detail', False)
|
||||||
part_detail = kwargs.pop('part_detail', True)
|
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)
|
location_detail = kwargs.pop('location_detail', False)
|
||||||
customer_detail = kwargs.pop('customer_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 company.models import Company, ManufacturerPart, SupplierPart
|
||||||
from InvenTree.api import (APIDownloadMixin, AttachmentMixin,
|
from InvenTree.api import (APIDownloadMixin, AttachmentMixin,
|
||||||
ListCreateDestroyAPIView)
|
ListCreateDestroyAPIView)
|
||||||
|
from InvenTree.filters import InvenTreeOrderingFilter
|
||||||
from InvenTree.helpers import DownloadFile, increment, isNull, str2bool
|
from InvenTree.helpers import DownloadFile, increment, isNull, str2bool
|
||||||
from InvenTree.mixins import (CreateAPI, ListAPI, ListCreateAPI, RetrieveAPI,
|
from InvenTree.mixins import (CreateAPI, ListAPI, ListCreateAPI, RetrieveAPI,
|
||||||
RetrieveUpdateAPI, RetrieveUpdateDestroyAPI,
|
RetrieveUpdateAPI, RetrieveUpdateDestroyAPI,
|
||||||
@ -1756,12 +1757,32 @@ class BomList(ListCreateDestroyAPIView):
|
|||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
DjangoFilterBackend,
|
||||||
filters.SearchFilter,
|
filters.SearchFilter,
|
||||||
filters.OrderingFilter,
|
InvenTreeOrderingFilter,
|
||||||
]
|
]
|
||||||
|
|
||||||
filterset_fields = [
|
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):
|
class BomImportUpload(CreateAPI):
|
||||||
"""API endpoint for uploading a complete Bill of Materials.
|
"""API endpoint for uploading a complete Bill of Materials.
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
part: 100
|
part: 100
|
||||||
sub_part: 5
|
sub_part: 5
|
||||||
quantity: 25
|
quantity: 25
|
||||||
|
reference: ABCDE
|
||||||
|
|
||||||
# 3 x Orphan
|
# 3 x Orphan
|
||||||
- model: part.bomitem
|
- model: part.bomitem
|
||||||
@ -32,6 +33,7 @@
|
|||||||
part: 100
|
part: 100
|
||||||
sub_part: 50
|
sub_part: 50
|
||||||
quantity: 3
|
quantity: 3
|
||||||
|
reference: VWXYZ
|
||||||
|
|
||||||
- model: part.bomitem
|
- model: part.bomitem
|
||||||
pk: 5
|
pk: 5
|
||||||
@ -39,6 +41,7 @@
|
|||||||
part: 1
|
part: 1
|
||||||
sub_part: 5
|
sub_part: 5
|
||||||
quantity: 3
|
quantity: 3
|
||||||
|
reference: LMNOP
|
||||||
|
|
||||||
# Make "Assembly" from "Bob"
|
# Make "Assembly" from "Bob"
|
||||||
- model: part.bomitem
|
- model: part.bomitem
|
||||||
|
@ -1647,6 +1647,102 @@ class BomItemTest(InvenTreeAPITestCase):
|
|||||||
for key in ['available_stock', 'available_substitute_stock']:
|
for key in ['available_stock', 'available_substitute_stock']:
|
||||||
self.assertTrue(key in el)
|
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):
|
def test_get_bom_detail(self):
|
||||||
"""Get the detail view for a single BomItem object."""
|
"""Get the detail view for a single BomItem object."""
|
||||||
url = reverse('api-bom-item-detail', kwargs={'pk': 3})
|
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) {
|
formatter: function(value, row, index, field) {
|
||||||
var text = '';
|
var text = '';
|
||||||
|
|
||||||
if (row.serial != null && row.quantity == 1) {
|
var item = row.item_detail;
|
||||||
text = `{% trans "Serial Number" %}: ${row.serial}`;
|
|
||||||
} else {
|
var text = `{% trans "Quantity" %}: ${row.quantity}`;
|
||||||
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}/`);
|
return renderLink(text, `/stock/item/${row.item}/`);
|
||||||
|
@ -94,9 +94,9 @@ distlib==0.3.4 \
|
|||||||
--hash=sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b \
|
--hash=sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b \
|
||||||
--hash=sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579
|
--hash=sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
django==3.2.13 \
|
django==3.2.14 \
|
||||||
--hash=sha256:6d93497a0a9bf6ba0e0b1a29cccdc40efbfc76297255b1309b3a884a688ec4b6 \
|
--hash=sha256:677182ba8b5b285a4e072f3ac17ceee6aff1b5ce77fd173cc5b6a2d3dc022fcf \
|
||||||
--hash=sha256:b896ca61edc079eb6bbaa15cf6071eb69d6aac08cce5211583cfb41515644fdf
|
--hash=sha256:a8681e098fa60f7c33a4b628d6fcd3fe983a0939ff1301ecacac21d0b38bad56
|
||||||
# via
|
# via
|
||||||
# -c requirements.txt
|
# -c requirements.txt
|
||||||
# django-debug-toolbar
|
# django-debug-toolbar
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Please keep this list sorted - if you pin a version provide a reason
|
# 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
|
coreapi # API documentation for djangorestframework
|
||||||
cryptography==3.4.8 # Core cryptographic functionality
|
cryptography==3.4.8 # Core cryptographic functionality
|
||||||
django-allauth # SSO for external providers via OpenID
|
django-allauth # SSO for external providers via OpenID
|
||||||
|
@ -42,7 +42,7 @@ defusedxml==0.7.1
|
|||||||
# python3-openid
|
# python3-openid
|
||||||
diff-match-patch==20200713
|
diff-match-patch==20200713
|
||||||
# via django-import-export
|
# via django-import-export
|
||||||
django==3.2.13
|
django==3.2.14
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# django-allauth
|
# django-allauth
|
||||||
|
Reference in New Issue
Block a user