2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-03-11 14:44:18 +00:00

Copy order params (#11479)

* Copy parameters when duplicating an order

* Add unit test for order parameter duplication

* Bunmp API version

* Fix test reliability

* Disable image fetching for SampleSupplierPlugin

- Allow turning on manually
- Prevent CI issues due to rate limiting

* Revery pypdf.. ???
This commit is contained in:
Oliver
2026-03-11 19:26:56 +11:00
committed by GitHub
parent bcde2706c7
commit 27809d712a
8 changed files with 102 additions and 10 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -1669,9 +1669,9 @@ pynacl==1.6.2 \
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# paramiko # paramiko
pypdf==6.8.0 \ pypdf==6.7.5 \
--hash=sha256:2a025080a8dd73f48123c89c57174a5ff3806c71763ee4e49572dc90454943c7 \ --hash=sha256:07ba7f1d6e6d9aa2a17f5452e320a84718d4ce863367f7ede2fd72280349ab13 \
--hash=sha256:cb7eaeaa4133ce76f762184069a854e03f4d9a08568f0e0623f7ea810407833b --hash=sha256:40bb2e2e872078655f12b9b89e2f900888bb505e88a82150b64f9f34fa25651d
# via # via
# -c src/backend/requirements.txt # -c src/backend/requirements.txt
# -r src/backend/requirements.in # -r src/backend/requirements.in

View File

@@ -292,7 +292,8 @@ export function usePurchaseOrderFields({
value: duplicateOrderId value: duplicateOrderId
}, },
copy_lines: {}, copy_lines: {},
copy_extra_lines: {} copy_extra_lines: {},
copy_parameters: {}
} }
}; };
} }

View File

@@ -91,7 +91,8 @@ export function useReturnOrderFields({
value: false, value: false,
hidden: true hidden: true
}, },
copy_extra_lines: {} copy_extra_lines: {},
copy_parameters: {}
} }
}; };
} }

View File

@@ -90,7 +90,8 @@ export function useSalesOrderFields({
value: duplicateOrderId value: duplicateOrderId
}, },
copy_lines: {}, copy_lines: {},
copy_extra_lines: {} copy_extra_lines: {},
copy_parameters: {}
} }
}; };
} }