mirror of
https://github.com/inventree/InvenTree.git
synced 2026-03-11 22:54:17 +00:00
Merge branch 'master' into dependabot/npm_and_yarn/src/frontend/dompurify-3.3.2
This commit is contained in:
@@ -1,11 +1,14 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 460
|
INVENTREE_API_VERSION = 461
|
||||||
"""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."""
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v461 -> 2026-03-10 : https://github.com/inventree/InvenTree/pull/11479
|
||||||
|
- Adds option to copy parameters when duplicating an order via the API
|
||||||
|
|
||||||
v460 -> 2026-02-25 : https://github.com/inventree/InvenTree/pull/11374
|
v460 -> 2026-02-25 : https://github.com/inventree/InvenTree/pull/11374
|
||||||
- Adds "updated_at" field to PurchaseOrder, SalesOrder and ReturnOrder API endpoints
|
- Adds "updated_at" field to PurchaseOrder, SalesOrder and ReturnOrder API endpoints
|
||||||
- Adds "updated_before" and "updated_after" date filters to all three order list endpoints
|
- Adds "updated_before" and "updated_after" date filters to all three order list endpoints
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class DuplicateOrderSerializer(serializers.Serializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
fields = ['order_id', 'copy_lines', 'copy_extra_lines']
|
fields = ['order_id', 'copy_lines', 'copy_extra_lines', 'copy_parameters']
|
||||||
|
|
||||||
order_id = serializers.IntegerField(
|
order_id = serializers.IntegerField(
|
||||||
required=True, label=_('Order ID'), help_text=_('ID of the order to duplicate')
|
required=True, label=_('Order ID'), help_text=_('ID of the order to duplicate')
|
||||||
@@ -95,6 +95,13 @@ class DuplicateOrderSerializer(serializers.Serializer):
|
|||||||
help_text=_('Copy extra line items from the original order'),
|
help_text=_('Copy extra line items from the original order'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
copy_parameters = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
default=True,
|
||||||
|
label=_('Copy Parameters'),
|
||||||
|
help_text=_('Copy order parameters from the original order'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AbstractOrderSerializer(
|
class AbstractOrderSerializer(
|
||||||
DataImportExportSerializerMixin, FilterableSerializerMixin, serializers.Serializer
|
DataImportExportSerializerMixin, FilterableSerializerMixin, serializers.Serializer
|
||||||
@@ -242,6 +249,7 @@ class AbstractOrderSerializer(
|
|||||||
order_id = duplicate.get('order_id', None)
|
order_id = duplicate.get('order_id', None)
|
||||||
copy_lines = duplicate.get('copy_lines', True)
|
copy_lines = duplicate.get('copy_lines', True)
|
||||||
copy_extra_lines = duplicate.get('copy_extra_lines', True)
|
copy_extra_lines = duplicate.get('copy_extra_lines', True)
|
||||||
|
copy_parameters = duplicate.get('copy_parameters', True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
copy_from = instance.__class__.objects.get(pk=order_id)
|
copy_from = instance.__class__.objects.get(pk=order_id)
|
||||||
@@ -260,6 +268,9 @@ class AbstractOrderSerializer(
|
|||||||
line.order = instance
|
line.order = instance
|
||||||
line.save()
|
line.save()
|
||||||
|
|
||||||
|
if copy_parameters:
|
||||||
|
instance.copy_parameters_from(copy_from)
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1574,6 +1574,65 @@ class SalesOrderTest(OrderTest):
|
|||||||
expected_code=201,
|
expected_code=201,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_so_duplicate(self):
|
||||||
|
"""Test SalesOrder duplication via the API."""
|
||||||
|
from common.models import Parameter, ParameterTemplate
|
||||||
|
|
||||||
|
url = reverse('api-so-list')
|
||||||
|
|
||||||
|
self.assignRole('sales_order.add')
|
||||||
|
|
||||||
|
so = models.SalesOrder.objects.get(pk=1)
|
||||||
|
self.assertEqual(so.status, SalesOrderStatus.PENDING)
|
||||||
|
|
||||||
|
# Add some parameters to the sales order
|
||||||
|
for idx in range(5):
|
||||||
|
template = ParameterTemplate.objects.create(name=f'Template {idx}')
|
||||||
|
|
||||||
|
Parameter.objects.create(
|
||||||
|
template=template,
|
||||||
|
model_type=so.get_content_type(),
|
||||||
|
model_id=so.pk,
|
||||||
|
data=f'Value {idx}',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(so.parameters.count(), 5)
|
||||||
|
|
||||||
|
# Create a duplicate of this sales order
|
||||||
|
# We explicitly specify "copy_parameters" as False, so the duplicated sales order should not have any parameters
|
||||||
|
response = self.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'reference': 'SO-12345',
|
||||||
|
'customer': so.customer.pk,
|
||||||
|
'duplicate': {'order_id': so.pk, 'copy_parameters': False},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
duplicate_id = response.data['pk']
|
||||||
|
duplicate_so = models.SalesOrder.objects.get(pk=duplicate_id)
|
||||||
|
|
||||||
|
self.assertEqual(duplicate_so.reference, 'SO-12345')
|
||||||
|
self.assertEqual(duplicate_so.customer, so.customer)
|
||||||
|
self.assertEqual(duplicate_so.parameters.count(), 0)
|
||||||
|
|
||||||
|
# Duplicate again, with default values for the "duplicate" options (which should result in parameters being copied)
|
||||||
|
response = self.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'reference': 'SO-12346',
|
||||||
|
'customer': so.customer.pk,
|
||||||
|
'duplicate': {'order_id': so.pk},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
duplicate_id = response.data['pk']
|
||||||
|
duplicate_so = models.SalesOrder.objects.get(pk=duplicate_id)
|
||||||
|
|
||||||
|
self.assertEqual(duplicate_so.reference, 'SO-12346')
|
||||||
|
self.assertEqual(duplicate_so.customer, so.customer)
|
||||||
|
self.assertEqual(duplicate_so.parameters.count(), 5)
|
||||||
|
|
||||||
def test_so_cancel(self):
|
def test_so_cancel(self):
|
||||||
"""Test API endpoint for cancelling a SalesOrder."""
|
"""Test API endpoint for cancelling a SalesOrder."""
|
||||||
so = models.SalesOrder.objects.get(pk=1)
|
so = models.SalesOrder.objects.get(pk=1)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
"""Sample supplier plugin."""
|
"""Sample supplier plugin."""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from company.models import Company, ManufacturerPart, SupplierPart, SupplierPriceBreak
|
from company.models import Company, ManufacturerPart, SupplierPart, SupplierPriceBreak
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
from plugin.mixins import SupplierMixin, supplier
|
from plugin.mixins import SupplierMixin, supplier
|
||||||
@@ -13,7 +15,16 @@ class SampleSupplierPlugin(SupplierMixin, InvenTreePlugin):
|
|||||||
SLUG = 'samplesupplier'
|
SLUG = 'samplesupplier'
|
||||||
TITLE = 'My sample supplier plugin'
|
TITLE = 'My sample supplier plugin'
|
||||||
|
|
||||||
VERSION = '0.0.1'
|
VERSION = '0.0.2'
|
||||||
|
|
||||||
|
SETTINGS = {
|
||||||
|
'DOWNLOAD_IMAGES': {
|
||||||
|
'name': 'Download part images',
|
||||||
|
'description': 'Enable downloading of part images during import (not recommended during testing)',
|
||||||
|
'validator': bool,
|
||||||
|
'default': False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize the sample supplier plugin."""
|
"""Initialize the sample supplier plugin."""
|
||||||
@@ -108,7 +119,12 @@ class SampleSupplierPlugin(SupplierMixin, InvenTreePlugin):
|
|||||||
|
|
||||||
# If the part was created, set additional fields
|
# If the part was created, set additional fields
|
||||||
if created:
|
if created:
|
||||||
if data['image_url']:
|
# Prevent downloading images during testing, as this can lead to unreliable tests
|
||||||
|
if (
|
||||||
|
data['image_url']
|
||||||
|
and not settings.TESTING
|
||||||
|
and self.get_setting('DOWNLOAD_IMAGES')
|
||||||
|
):
|
||||||
file, fmt = self.download_image(data['image_url'])
|
file, fmt = self.download_image(data['image_url'])
|
||||||
filename = f'part_{part.pk}_image.{fmt.lower()}'
|
filename = f'part_{part.pk}_image.{fmt.lower()}'
|
||||||
part.image.save(filename, file)
|
part.image.save(filename, file)
|
||||||
|
|||||||
@@ -292,7 +292,8 @@ export function usePurchaseOrderFields({
|
|||||||
value: duplicateOrderId
|
value: duplicateOrderId
|
||||||
},
|
},
|
||||||
copy_lines: {},
|
copy_lines: {},
|
||||||
copy_extra_lines: {}
|
copy_extra_lines: {},
|
||||||
|
copy_parameters: {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ export function useReturnOrderFields({
|
|||||||
value: false,
|
value: false,
|
||||||
hidden: true
|
hidden: true
|
||||||
},
|
},
|
||||||
copy_extra_lines: {}
|
copy_extra_lines: {},
|
||||||
|
copy_parameters: {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ export function useSalesOrderFields({
|
|||||||
value: duplicateOrderId
|
value: duplicateOrderId
|
||||||
},
|
},
|
||||||
copy_lines: {},
|
copy_lines: {},
|
||||||
copy_extra_lines: {}
|
copy_extra_lines: {},
|
||||||
|
copy_parameters: {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user