mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-02 03:30:54 +00:00
Adds option for duplicating an existing purchase order (#3567)
* Adds option for duplicating an existing purchase order - Copy line items across * Update API version * JS linting * Adds option for duplication of extra lines * Decimal point rounding fix * Unit tests for PO duplication via the API
This commit is contained in:
@ -1,10 +1,13 @@
|
||||
"""JSON API for the Order app."""
|
||||
|
||||
from django.db import transaction
|
||||
from django.db.models import F, Q
|
||||
from django.urls import include, path, re_path
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django_filters import rest_framework as rest_filters
|
||||
from rest_framework import filters, status
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.response import Response
|
||||
|
||||
import order.models as models
|
||||
@ -116,12 +119,48 @@ class PurchaseOrderList(APIDownloadMixin, ListCreateAPI):
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""Save user information on create."""
|
||||
serializer = self.get_serializer(data=self.clean_data(request.data))
|
||||
|
||||
data = self.clean_data(request.data)
|
||||
|
||||
duplicate_order = data.pop('duplicate_order', None)
|
||||
duplicate_line_items = str2bool(data.pop('duplicate_line_items', False))
|
||||
duplicate_extra_lines = str2bool(data.pop('duplicate_extra_lines', False))
|
||||
|
||||
if duplicate_order is not None:
|
||||
try:
|
||||
duplicate_order = models.PurchaseOrder.objects.get(pk=duplicate_order)
|
||||
except (ValueError, models.PurchaseOrder.DoesNotExist):
|
||||
raise ValidationError({
|
||||
'duplicate_order': [_('No matching purchase order found')],
|
||||
})
|
||||
|
||||
serializer = self.get_serializer(data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
item = serializer.save()
|
||||
item.created_by = request.user
|
||||
item.save()
|
||||
with transaction.atomic():
|
||||
order = serializer.save()
|
||||
order.created_by = request.user
|
||||
order.save()
|
||||
|
||||
# Duplicate line items from other order if required
|
||||
if duplicate_order is not None:
|
||||
|
||||
if duplicate_line_items:
|
||||
for line in duplicate_order.lines.all():
|
||||
# Copy the line across to the new order
|
||||
line.pk = None
|
||||
line.order = order
|
||||
line.received = 0
|
||||
|
||||
line.save()
|
||||
|
||||
if duplicate_extra_lines:
|
||||
for line in duplicate_order.extra_lines.all():
|
||||
# Copy the line across to the new order
|
||||
line.pk = None
|
||||
line.order = order
|
||||
|
||||
line.save()
|
||||
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
|
@ -46,6 +46,9 @@
|
||||
{% if order.can_cancel %}
|
||||
<li><a class='dropdown-item' href='#' id='cancel-order'><span class='fas fa-times-circle icon-red'></span> {% trans "Cancel order" %}</a></li>
|
||||
{% endif %}
|
||||
{% if roles.purchase_order.add %}
|
||||
<li><a class='dropdown-item' href='#' id='duplicate-order'><span class='fas fa-clone'></span> {% trans "Duplicate order" %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% if order.status == PurchaseOrderStatus.PENDING %}
|
||||
@ -217,6 +220,8 @@ $('#print-order-report').click(function() {
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
{% if roles.purchase_order.change %}
|
||||
|
||||
$("#edit-order").click(function() {
|
||||
|
||||
editPurchaseOrder({{ order.pk }}, {
|
||||
@ -275,6 +280,16 @@ $("#cancel-order").click(function() {
|
||||
);
|
||||
});
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if roles.purchase_order.add %}
|
||||
$('#duplicate-order').click(function() {
|
||||
duplicatePurchaseOrder(
|
||||
{{ order.pk }},
|
||||
);
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
$("#export-order").click(function() {
|
||||
exportOrder('{% url "po-export" order.id %}');
|
||||
});
|
||||
|
@ -225,6 +225,64 @@ class PurchaseOrderTest(OrderTest):
|
||||
expected_code=201
|
||||
)
|
||||
|
||||
def test_po_duplicate(self):
|
||||
"""Test that we can duplicate a PurchaseOrder via the API"""
|
||||
|
||||
self.assignRole('purchase_order.add')
|
||||
|
||||
po = models.PurchaseOrder.objects.get(pk=1)
|
||||
|
||||
self.assertTrue(po.lines.count() > 0)
|
||||
|
||||
# Add some extra line items to this order
|
||||
for idx in range(5):
|
||||
models.PurchaseOrderExtraLine.objects.create(
|
||||
order=po,
|
||||
quantity=idx + 10,
|
||||
reference='some reference',
|
||||
)
|
||||
|
||||
data = self.get(reverse('api-po-detail', kwargs={'pk': 1})).data
|
||||
|
||||
del data['pk']
|
||||
del data['reference']
|
||||
|
||||
data['duplicate_order'] = 1
|
||||
data['duplicate_line_items'] = True
|
||||
data['duplicate_extra_lines'] = False
|
||||
|
||||
data['reference'] = 'PO-9999'
|
||||
|
||||
# Duplicate via the API
|
||||
response = self.post(
|
||||
reverse('api-po-list'),
|
||||
data,
|
||||
expected_code=201
|
||||
)
|
||||
|
||||
# Order is for the same supplier
|
||||
self.assertEqual(response.data['supplier'], po.supplier.pk)
|
||||
|
||||
po_dup = models.PurchaseOrder.objects.get(pk=response.data['pk'])
|
||||
|
||||
self.assertEqual(po_dup.extra_lines.count(), 0)
|
||||
self.assertEqual(po_dup.lines.count(), po.lines.count())
|
||||
|
||||
data['reference'] = 'PO-9998'
|
||||
data['duplicate_line_items'] = False
|
||||
data['duplicate_extra_lines'] = True
|
||||
|
||||
response = self.post(
|
||||
reverse('api-po-list'),
|
||||
data,
|
||||
expected_code=201,
|
||||
)
|
||||
|
||||
po_dup = models.PurchaseOrder.objects.get(pk=response.data['pk'])
|
||||
|
||||
self.assertEqual(po_dup.extra_lines.count(), po.extra_lines.count())
|
||||
self.assertEqual(po_dup.lines.count(), 0)
|
||||
|
||||
def test_po_cancel(self):
|
||||
"""Test the PurchaseOrderCancel API endpoint."""
|
||||
po = models.PurchaseOrder.objects.get(pk=1)
|
||||
|
Reference in New Issue
Block a user