Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
"""InvenTree API version information."""

# 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."""

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
- 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
Expand Down
13 changes: 12 additions & 1 deletion src/backend/InvenTree/order/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class DuplicateOrderSerializer(serializers.Serializer):
class Meta:
"""Metaclass options."""

fields = ['order_id', 'copy_lines', 'copy_extra_lines']
fields = ['order_id', 'copy_lines', 'copy_extra_lines', 'copy_parameters']

order_id = serializers.IntegerField(
required=True, label=_('Order ID'), help_text=_('ID of the order to duplicate')
Expand All @@ -95,6 +95,13 @@ class Meta:
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(
DataImportExportSerializerMixin, FilterableSerializerMixin, serializers.Serializer
Expand Down Expand Up @@ -242,6 +249,7 @@ def create(self, validated_data):
order_id = duplicate.get('order_id', None)
copy_lines = duplicate.get('copy_lines', True)
copy_extra_lines = duplicate.get('copy_extra_lines', True)
copy_parameters = duplicate.get('copy_parameters', True)

try:
copy_from = instance.__class__.objects.get(pk=order_id)
Expand All @@ -260,6 +268,9 @@ def create(self, validated_data):
line.order = instance
line.save()

if copy_parameters:
instance.copy_parameters_from(copy_from)

return instance


Expand Down
59 changes: 59 additions & 0 deletions src/backend/InvenTree/order/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,65 @@ def test_so_create(self):
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):
"""Test API endpoint for cancelling a SalesOrder."""
so = models.SalesOrder.objects.get(pk=1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Sample supplier plugin."""

from django.conf import settings

from company.models import Company, ManufacturerPart, SupplierPart, SupplierPriceBreak
from part.models import Part
from plugin.mixins import SupplierMixin, supplier
Expand Down Expand Up @@ -108,7 +110,8 @@ def import_part(self, data, **kwargs) -> Part:

# If the part was created, set additional fields
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:
file, fmt = self.download_image(data['image_url'])
filename = f'part_{part.pk}_image.{fmt.lower()}'
part.image.save(filename, file)
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/src/forms/PurchaseOrderForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,8 @@ export function usePurchaseOrderFields({
value: duplicateOrderId
},
copy_lines: {},
copy_extra_lines: {}
copy_extra_lines: {},
copy_parameters: {}
}
};
}
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/src/forms/ReturnOrderForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ export function useReturnOrderFields({
value: false,
hidden: true
},
copy_extra_lines: {}
copy_extra_lines: {},
copy_parameters: {}
}
};
}
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/src/forms/SalesOrderForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export function useSalesOrderFields({
value: duplicateOrderId
},
copy_lines: {},
copy_extra_lines: {}
copy_extra_lines: {},
copy_parameters: {}
}
};
}
Expand Down
Loading