mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	Refactor POLineItemCreate form
This commit is contained in:
		| @@ -20,7 +20,7 @@ from common.forms import MatchItemForm | |||||||
| import part.models | import part.models | ||||||
|  |  | ||||||
| from stock.models import StockLocation | from stock.models import StockLocation | ||||||
| from .models import PurchaseOrder, PurchaseOrderLineItem | from .models import PurchaseOrder | ||||||
| from .models import SalesOrder, SalesOrderLineItem | from .models import SalesOrder, SalesOrderLineItem | ||||||
| from .models import SalesOrderAllocation | from .models import SalesOrderAllocation | ||||||
|  |  | ||||||
| @@ -96,24 +96,6 @@ class ReceivePurchaseOrderForm(HelperForm): | |||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
| class EditPurchaseOrderLineItemForm(HelperForm): |  | ||||||
|     """ Form for editing a PurchaseOrderLineItem object """ |  | ||||||
|  |  | ||||||
|     quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity')) |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|         model = PurchaseOrderLineItem |  | ||||||
|         fields = [ |  | ||||||
|             'order', |  | ||||||
|             'part', |  | ||||||
|             'quantity', |  | ||||||
|             'reference', |  | ||||||
|             'purchase_price', |  | ||||||
|             'destination', |  | ||||||
|             'notes', |  | ||||||
|         ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class EditSalesOrderLineItemForm(HelperForm): | class EditSalesOrderLineItemForm(HelperForm): | ||||||
|     """ Form for editing a SalesOrderLineItem object """ |     """ Form for editing a SalesOrderLineItem object """ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -350,7 +350,7 @@ class SOLineItemSerializer(InvenTreeModelSerializer): | |||||||
|         max_digits=19, |         max_digits=19, | ||||||
|         decimal_places=4, |         decimal_places=4, | ||||||
|         allow_null=True |         allow_null=True | ||||||
|     )     |     ) | ||||||
|  |  | ||||||
|     sale_price_string = serializers.CharField(source='sale_price', read_only=True) |     sale_price_string = serializers.CharField(source='sale_price', read_only=True) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -38,26 +38,34 @@ | |||||||
|  |  | ||||||
| {% if order.status == PurchaseOrderStatus.PENDING %} | {% if order.status == PurchaseOrderStatus.PENDING %} | ||||||
| $('#new-po-line').click(function() { | $('#new-po-line').click(function() { | ||||||
|     launchModalForm("{% url 'po-line-item-create' %}", |  | ||||||
|         { |  | ||||||
|             reload: true, |     constructForm('{% url "api-po-line-list" %}', { | ||||||
|             data: { |         fields: { | ||||||
|                 order: {{ order.id }}, |             order: { | ||||||
|  |                 value: {{ order.pk }}, | ||||||
|  |                 hidden: true, | ||||||
|             }, |             }, | ||||||
|             secondary: [ |             part: { | ||||||
|                 { |                 filters: { | ||||||
|                     field: 'part', |                     part_detail: true, | ||||||
|                     label: '{% trans "New Supplier Part" %}', |                     supplier_detail: true, | ||||||
|                     title: '{% trans "Create new supplier part" %}', |                     supplier: {{ order.supplier.pk }}, | ||||||
|                     url: "{% url 'supplier-part-create' %}", |  | ||||||
|                     data: { |  | ||||||
|                         supplier: {{ order.supplier.id }}, |  | ||||||
|                     }, |  | ||||||
|                 }, |                 }, | ||||||
|             ], |             }, | ||||||
|         } |             quantity: {}, | ||||||
|     ); |             reference: {}, | ||||||
|  |             purchase_price: {}, | ||||||
|  |             purchase_price_currency: {}, | ||||||
|  |             destination: {}, | ||||||
|  |             notes: {}, | ||||||
|  |         }, | ||||||
|  |         method: 'POST', | ||||||
|  |         title: '{% trans "Add Line Item" %}', | ||||||
|  |         onSuccess: reloadTable, | ||||||
|  |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| {% endif %} | {% endif %} | ||||||
|  |  | ||||||
| function reloadTable() { | function reloadTable() { | ||||||
| @@ -77,6 +85,7 @@ function setupCallbacks() { | |||||||
|             fields: { |             fields: { | ||||||
|                 part: { |                 part: { | ||||||
|                     filters: { |                     filters: { | ||||||
|  |                         part_detail: true, | ||||||
|                         supplier_detail: true, |                         supplier_detail: true, | ||||||
|                         supplier: {{ order.supplier.pk }}, |                         supplier: {{ order.supplier.pk }}, | ||||||
|                     } |                     } | ||||||
|   | |||||||
| @@ -104,57 +104,6 @@ class POTests(OrderViewTestCase): | |||||||
|         order = PurchaseOrder.objects.get(pk=1) |         order = PurchaseOrder.objects.get(pk=1) | ||||||
|         self.assertEqual(order.status, PurchaseOrderStatus.PLACED) |         self.assertEqual(order.status, PurchaseOrderStatus.PLACED) | ||||||
|  |  | ||||||
|     def test_line_item_create(self): |  | ||||||
|         """ Test the form for adding a new LineItem to a PurchaseOrder """ |  | ||||||
|  |  | ||||||
|         # Record the number of line items in the PurchaseOrder |  | ||||||
|         po = PurchaseOrder.objects.get(pk=1) |  | ||||||
|         n = po.lines.count() |  | ||||||
|         self.assertEqual(po.status, PurchaseOrderStatus.PENDING) |  | ||||||
|  |  | ||||||
|         url = reverse('po-line-item-create') |  | ||||||
|  |  | ||||||
|         # GET the form (pass the correct info) |  | ||||||
|         response = self.client.get(url, {'order': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') |  | ||||||
|  |  | ||||||
|         post_data = { |  | ||||||
|             'part': 100, |  | ||||||
|             'quantity': 45, |  | ||||||
|             'reference': 'Test reference field', |  | ||||||
|             'notes': 'Test notes field' |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         # POST with an invalid purchase order |  | ||||||
|         post_data['order'] = 99 |  | ||||||
|         response = self.client.post(url, post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') |  | ||||||
|         data = json.loads(response.content) |  | ||||||
|         self.assertFalse(data['form_valid']) |  | ||||||
|  |  | ||||||
|         # POST with a part that does not match the purchase order |  | ||||||
|         post_data['order'] = 1 |  | ||||||
|         post_data['part'] = 7 |  | ||||||
|         response = self.client.post(url, post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') |  | ||||||
|         data = json.loads(response.content) |  | ||||||
|         self.assertFalse(data['form_valid']) |  | ||||||
|  |  | ||||||
|         # POST with an invalid part |  | ||||||
|         post_data['part'] = 12345 |  | ||||||
|         response = self.client.post(url, post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') |  | ||||||
|         data = json.loads(response.content) |  | ||||||
|         self.assertFalse(data['form_valid']) |  | ||||||
|  |  | ||||||
|         # POST the form with valid data |  | ||||||
|         post_data['part'] = 100 |  | ||||||
|         response = self.client.post(url, post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') |  | ||||||
|         self.assertEqual(response.status_code, 200) |  | ||||||
|         data = json.loads(response.content) |  | ||||||
|         self.assertTrue(data['form_valid']) |  | ||||||
|  |  | ||||||
|         self.assertEqual(n + 1, PurchaseOrder.objects.get(pk=1).lines.count()) |  | ||||||
|  |  | ||||||
|         line = PurchaseOrderLineItem.objects.get(order=1, part=100) |  | ||||||
|         self.assertEqual(line.quantity, 45) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestPOReceive(OrderViewTestCase): | class TestPOReceive(OrderViewTestCase): | ||||||
|     """ Tests for receiving a purchase order """ |     """ Tests for receiving a purchase order """ | ||||||
|   | |||||||
| @@ -1049,90 +1049,6 @@ class OrderParts(AjaxView): | |||||||
|                 order.add_line_item(supplier_part, quantity, purchase_price=purchase_price) |                 order.add_line_item(supplier_part, quantity, purchase_price=purchase_price) | ||||||
|  |  | ||||||
|  |  | ||||||
| class POLineItemCreate(AjaxCreateView): |  | ||||||
|     """ AJAX view for creating a new PurchaseOrderLineItem object |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     model = PurchaseOrderLineItem |  | ||||||
|     context_object_name = 'line' |  | ||||||
|     form_class = order_forms.EditPurchaseOrderLineItemForm |  | ||||||
|     ajax_form_title = _('Add Line Item') |  | ||||||
|  |  | ||||||
|     def validate(self, item, form, **kwargs): |  | ||||||
|  |  | ||||||
|         order = form.cleaned_data.get('order', None) |  | ||||||
|  |  | ||||||
|         part = form.cleaned_data.get('part', None) |  | ||||||
|  |  | ||||||
|         if not part: |  | ||||||
|             form.add_error('part', _('Supplier part must be specified')) |  | ||||||
|  |  | ||||||
|         if part and order: |  | ||||||
|             if not part.supplier == order.supplier: |  | ||||||
|                 form.add_error( |  | ||||||
|                     'part', |  | ||||||
|                     _('Supplier must match for Part and Order') |  | ||||||
|                 ) |  | ||||||
|  |  | ||||||
|     def get_form(self): |  | ||||||
|         """ Limit choice options based on the selected order, etc |  | ||||||
|         """ |  | ||||||
|  |  | ||||||
|         form = super().get_form() |  | ||||||
|  |  | ||||||
|         # Limit the available to orders to ones that are PENDING |  | ||||||
|         query = form.fields['order'].queryset |  | ||||||
|         query = query.filter(status=PurchaseOrderStatus.PENDING) |  | ||||||
|         form.fields['order'].queryset = query |  | ||||||
|  |  | ||||||
|         order_id = form['order'].value() |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             order = PurchaseOrder.objects.get(id=order_id) |  | ||||||
|  |  | ||||||
|             query = form.fields['part'].queryset |  | ||||||
|  |  | ||||||
|             # Only allow parts from the selected supplier |  | ||||||
|             query = query.filter(supplier=order.supplier.id) |  | ||||||
|  |  | ||||||
|             exclude = [] |  | ||||||
|  |  | ||||||
|             for line in order.lines.all(): |  | ||||||
|                 if line.part and line.part.id not in exclude: |  | ||||||
|                     exclude.append(line.part.id) |  | ||||||
|  |  | ||||||
|             # Remove parts that are already in the order |  | ||||||
|             query = query.exclude(id__in=exclude) |  | ||||||
|  |  | ||||||
|             form.fields['part'].queryset = query |  | ||||||
|             form.fields['order'].widget = HiddenInput() |  | ||||||
|         except (ValueError, PurchaseOrder.DoesNotExist): |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         return form |  | ||||||
|  |  | ||||||
|     def get_initial(self): |  | ||||||
|         """ Extract initial data for the line item. |  | ||||||
|  |  | ||||||
|         - The 'order' will be passed as a query parameter |  | ||||||
|         - Use this to set the 'order' field and limit the options for 'part' |  | ||||||
|         """ |  | ||||||
|  |  | ||||||
|         initials = super().get_initial().copy() |  | ||||||
|  |  | ||||||
|         order_id = self.request.GET.get('order', None) |  | ||||||
|  |  | ||||||
|         if order_id: |  | ||||||
|             try: |  | ||||||
|                 order = PurchaseOrder.objects.get(id=order_id) |  | ||||||
|                 initials['order'] = order |  | ||||||
|  |  | ||||||
|             except (PurchaseOrder.DoesNotExist, ValueError): |  | ||||||
|                 pass |  | ||||||
|  |  | ||||||
|         return initials |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SOLineItemCreate(AjaxCreateView): | class SOLineItemCreate(AjaxCreateView): | ||||||
|     """ Ajax view for creating a new SalesOrderLineItem object """ |     """ Ajax view for creating a new SalesOrderLineItem object """ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -145,9 +145,11 @@ function renderSupplierPart(name, data, parameters, options) { | |||||||
|     var html = `<img src='${image}' class='select2-thumbnail'>`; |     var html = `<img src='${image}' class='select2-thumbnail'>`; | ||||||
|      |      | ||||||
|     html += ` <span><b>${data.supplier_detail.name}</b> - ${data.SKU}</span>`; |     html += ` <span><b>${data.supplier_detail.name}</b> - ${data.SKU}</span>`; | ||||||
|  |     html += ` - <i>${data.part_detail.full_name}</i>`; | ||||||
|  |  | ||||||
|     html += `<span class='float-right'>{% trans "Supplier Part ID" %}: ${data.pk}</span>`; |     html += `<span class='float-right'>{% trans "Supplier Part ID" %}: ${data.pk}</span>`; | ||||||
|  |  | ||||||
|  |  | ||||||
|     return html; |     return html; | ||||||
|  |  | ||||||
| } | } | ||||||
		Reference in New Issue
	
	Block a user