diff --git a/InvenTree/InvenTree/api_tester.py b/InvenTree/InvenTree/api_tester.py index eb92bd80c1..f0f33ad1a5 100644 --- a/InvenTree/InvenTree/api_tester.py +++ b/InvenTree/InvenTree/api_tester.py @@ -73,22 +73,50 @@ class InvenTreeAPITestCase(APITestCase): ruleset.save() break - def get(self, url, data={}, code=200): + def get(self, url, data={}, expected_code=200): """ Issue a GET request """ response = self.client.get(url, data, format='json') - self.assertEqual(response.status_code, code) + if expected_code is not None: + self.assertEqual(response.status_code, expected_code) return response - def post(self, url, data): + def post(self, url, data, expected_code=None): """ Issue a POST request """ response = self.client.post(url, data=data, format='json') + if expected_code is not None: + self.assertEqual(response.status_code, expected_code) + + return response + + def delete(self, url, expected_code=None): + """ + Issue a DELETE request + """ + + response = self.client.delete(url) + + if expected_code is not None: + self.assertEqual(response.status_code, expected_code) + + return response + + def patch(self, url, data, expected_code=None): + """ + Issue a PATCH request + """ + + response = self.client.patch(url, data=data, format='json') + + if expected_code is not None: + self.assertEqual(response.status_code, expected_code) + return response diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 6661bd568b..c22d76a52e 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -157,7 +157,7 @@ class POList(generics.ListCreateAPIView): ordering = '-creation_date' -class PODetail(generics.RetrieveUpdateAPIView): +class PODetail(generics.RetrieveUpdateDestroyAPIView): """ API endpoint for detail view of a PurchaseOrder object """ queryset = PurchaseOrder.objects.all() @@ -382,7 +382,7 @@ class SOList(generics.ListCreateAPIView): ordering = '-creation_date' -class SODetail(generics.RetrieveUpdateAPIView): +class SODetail(generics.RetrieveUpdateDestroyAPIView): """ API endpoint for detail view of a SalesOrder object. """ diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 9efbf947bb..821e8bf343 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -93,8 +93,10 @@ class POSerializer(InvenTreeModelSerializer): ] read_only_fields = [ - 'reference', 'status' + 'issue_date', + 'complete_date', + 'creation_date', ] @@ -110,8 +112,9 @@ class POLineItemSerializer(InvenTreeModelSerializer): self.fields.pop('part_detail') self.fields.pop('supplier_part_detail') - quantity = serializers.FloatField() - received = serializers.FloatField() + # TODO: Once https://github.com/inventree/InvenTree/issues/1687 is fixed, remove default values + quantity = serializers.FloatField(default=1) + received = serializers.FloatField(default=0) part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True) supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True) @@ -226,8 +229,9 @@ class SalesOrderSerializer(InvenTreeModelSerializer): ] read_only_fields = [ - 'reference', - 'status' + 'status', + 'creation_date', + 'shipment_date', ] @@ -313,7 +317,9 @@ class SOLineItemSerializer(InvenTreeModelSerializer): part_detail = PartBriefSerializer(source='part', many=False, read_only=True) allocations = SalesOrderAllocationSerializer(many=True, read_only=True) - quantity = serializers.FloatField() + # TODO: Once https://github.com/inventree/InvenTree/issues/1687 is fixed, remove default values + quantity = serializers.FloatField(default=1) + allocated = serializers.FloatField(source='allocated_quantity', read_only=True) fulfilled = serializers.FloatField(source='fulfilled_quantity', read_only=True) sale_price_string = serializers.CharField(source='sale_price', read_only=True) diff --git a/InvenTree/order/test_api.py b/InvenTree/order/test_api.py index ee77f429e1..24ca8581d9 100644 --- a/InvenTree/order/test_api.py +++ b/InvenTree/order/test_api.py @@ -110,6 +110,96 @@ class PurchaseOrderTest(OrderTest): self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_po_operations(self): + """ + Test that we can create / edit and delete a PurchaseOrder via the API + """ + + n = PurchaseOrder.objects.count() + + url = reverse('api-po-list') + + # Initially we do not have "add" permission for the PurchaseOrder model, + # so this POST request should return 403 + response = self.post( + url, + { + 'supplier': 1, + 'reference': '123456789-xyz', + 'description': 'PO created via the API', + }, + expected_code=403 + ) + + # And no new PurchaseOrder objects should have been created + self.assertEqual(PurchaseOrder.objects.count(), n) + + # Ok, now let's give this user the correct permission + self.assignRole('purchase_order.add') + + # Initially we do not have "add" permission for the PurchaseOrder model, + # so this POST request should return 403 + response = self.post( + url, + { + 'supplier': 1, + 'reference': '123456789-xyz', + 'description': 'PO created via the API', + }, + expected_code=201 + ) + + self.assertEqual(PurchaseOrder.objects.count(), n + 1) + + pk = response.data['pk'] + + # Try to create a PO with identical reference (should fail!) + response = self.post( + url, + { + 'supplier': 1, + 'reference': '123456789-xyz', + 'description': 'A different description', + }, + expected_code=400 + ) + + self.assertEqual(PurchaseOrder.objects.count(), n + 1) + + url = reverse('api-po-detail', kwargs={'pk': pk}) + + # Get detail info! + response = self.get(url) + self.assertEqual(response.data['pk'], pk) + self.assertEqual(response.data['reference'], '123456789-xyz') + + # Try to alter (edit) the PurchaseOrder + response = self.patch( + url, + { + 'reference': '12345-abc', + }, + expected_code=200 + ) + + # Reference should have changed + self.assertEqual(response.data['reference'], '12345-abc') + + # Now, let's try to delete it! + # Initially, we do *not* have the required permission! + response = self.delete(url, expected_code=403) + + # Now, add the "delete" permission! + self.assignRole("purchase_order.delete") + + response = self.delete(url, expected_code=204) + + # Number of PurchaseOrder objects should have decreased + self.assertEqual(PurchaseOrder.objects.count(), n) + + # And if we try to access the detail view again, it has gone + response = self.get(url, expected_code=404) + class SalesOrderTest(OrderTest): """ @@ -158,8 +248,6 @@ class SalesOrderTest(OrderTest): response = self.get(url) - self.assertEqual(response.status_code, 200) - data = response.data self.assertEqual(data['pk'], 1) @@ -168,6 +256,87 @@ class SalesOrderTest(OrderTest): url = reverse('api-so-attachment-list') - response = self.get(url) + self.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_so_operations(self): + """ + Test that we can create / edit and delete a SalesOrder via the API + """ + + n = SalesOrder.objects.count() + + url = reverse('api-so-list') + + # Initially we do not have "add" permission for the SalesOrder model, + # so this POST request should return 403 (denied) + response = self.post( + url, + { + 'customer': 4, + 'reference': '12345', + 'description': 'Sales order', + }, + expected_code=403, + ) + + self.assignRole('sales_order.add') + + # Now we should be able to create a SalesOrder via the API + response = self.post( + url, + { + 'customer': 4, + 'reference': '12345', + 'description': 'Sales order', + }, + expected_code=201 + ) + + # Check that the new order has been created + self.assertEqual(SalesOrder.objects.count(), n + 1) + + # Grab the PK for the newly created SalesOrder + pk = response.data['pk'] + + # Try to create a SO with identical reference (should fail) + response = self.post( + url, + { + 'customer': 4, + 'reference': '12345', + 'description': 'Another sales order', + }, + expected_code=400 + ) + + url = reverse('api-so-detail', kwargs={'pk': pk}) + + # Extract detail info for the SalesOrder + response = self.get(url) + self.assertEqual(response.data['reference'], '12345') + + # Try to alter (edit) the SalesOrder + response = self.patch( + url, + { + 'reference': '12345-a', + }, + expected_code=200 + ) + + # Reference should have changed + self.assertEqual(response.data['reference'], '12345-a') + + # Now, let's try to delete this SalesOrder + # Initially, we do not have the required permission + response = self.delete(url, expected_code=403) + + self.assignRole('sales_order.delete') + + response = self.delete(url, expected_code=204) + + # Check that the number of sales orders has decreased + self.assertEqual(SalesOrder.objects.count(), n) + + # And the resource should no longer be available + response = self.get(url, expected_code=404)