diff --git a/InvenTree/order/templates/order/order_wizard/select_parts.html b/InvenTree/order/templates/order/order_wizard/select_parts.html deleted file mode 100644 index 9d0ccdfb82..0000000000 --- a/InvenTree/order/templates/order/order_wizard/select_parts.html +++ /dev/null @@ -1,85 +0,0 @@ -{% extends "modal_form.html" %} - -{% load inventree_extras %} -{% load i18n %} - -{% block form %} -{% default_currency as currency %} -{% settings_value 'PART_SHOW_PRICE_IN_FORMS' as show_price %} - -

- {% trans "Step 1 of 2 - Select Part Suppliers" %} -

- -{% if parts|length > 0 %} - -{% else %} - -{% endif %} - -
- {% csrf_token %} - {% load crispy_forms_tags %} - - - - - - - - - - - {% for part in parts %} - - - - - - - - {% endfor %} -
{% trans "Part" %}{% trans "Select Supplier" %}{% trans "Quantity" %}
- {% include "hover_image.html" with image=part.image hover=False %} - {{ part.full_name }} {{ part.description }} - - - -
-
- -
- {% if not part.order_supplier %} - {% blocktrans with name=part.name %}Select a supplier for {{name}}{% endblocktrans %} - {% endif %} -
-
-
-
- -
-
-
- -
-
-{% endblock %} \ No newline at end of file diff --git a/InvenTree/order/templates/order/order_wizard/select_pos.html b/InvenTree/order/templates/order/order_wizard/select_pos.html deleted file mode 100644 index 6ef2f6c910..0000000000 --- a/InvenTree/order/templates/order/order_wizard/select_pos.html +++ /dev/null @@ -1,77 +0,0 @@ -{% extends "modal_form.html" %} - -{% load i18n %} - -{% block form %} - -

- {% trans "Step 2 of 2 - Select Purchase Orders" %} -

- - - -
- {% csrf_token %} - {% load crispy_forms_tags %} - - - - {% for supplier in suppliers %} - {% for item in supplier.order_items %} - - - {% endfor %} - {% endfor %} - - - - - - - - {% for supplier in suppliers %} - - - - - - - {% endfor %} - -
{% trans "Supplier" %}{% trans "Items" %}{% trans "Select Purchase Order" %}
- {% include 'hover_image.html' with image=supplier.image hover=False %} - {{ supplier.name }} - {{ supplier.order_items|length }} - - -
-
- -
- {% if not supplier.selected_purchase_order %} - {% blocktrans with name=supplier.name %}Select a purchase order for {{name}}{% endblocktrans %} - {% endif %} -
-
-
-{% endblock %} \ No newline at end of file diff --git a/InvenTree/order/urls.py b/InvenTree/order/urls.py index 54be93905f..a2a2897da2 100644 --- a/InvenTree/order/urls.py +++ b/InvenTree/order/urls.py @@ -22,8 +22,7 @@ purchase_order_detail_urls = [ ] purchase_order_urls = [ - - re_path(r'^order-parts/', views.OrderParts.as_view(), name='order-parts'), + re_path(r'^pricing/', views.LineItemPricing.as_view(), name='line-pricing'), # Display detail view for a single purchase order diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index 35f8b973f4..15bff617d1 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -448,346 +448,6 @@ class PurchaseOrderExport(AjaxView): return DownloadFile(filedata, filename) -class OrderParts(AjaxView): - """ View for adding various SupplierPart items to a Purchase Order. - - SupplierParts can be selected from a variety of 'sources': - - - ?supplier_parts[]= -> Direct list of SupplierPart objects - - ?parts[]= -> List of base Part objects (user must then select supplier parts) - - ?stock[]= -> List of StockItem objects (user must select supplier parts) - - ?build= -> A Build object (user must select parts, then supplier parts) - - """ - - ajax_form_title = _("Order Parts") - ajax_template_name = 'order/order_wizard/select_parts.html' - - role_required = [ - 'part.view', - 'purchase_order.change', - ] - - # List of Parts we wish to order - parts = [] - suppliers = [] - - def get_context_data(self): - - ctx = {} - - ctx['parts'] = sorted(self.parts, key=lambda part: int(part.order_quantity), reverse=True) - ctx['suppliers'] = self.suppliers - - return ctx - - def get_data(self): - """ enrich respone json data """ - data = super().get_data() - # if in selection-phase, add a button to update the prices - if getattr(self, 'form_step', 'select_parts') == 'select_parts': - data['buttons'] = [{'name': 'update_price', 'title': _('Update prices')}] # set buttons - data['hideErrorMessage'] = '1' # hide the error message - return data - - def get_suppliers(self): - """ Calculates a list of suppliers which the user will need to create PurchaseOrders for. - This is calculated AFTER the user finishes selecting the parts to order. - Crucially, get_parts() must be called before get_suppliers() - """ - - suppliers = {} - - for supplier in self.suppliers: - supplier.order_items = [] - - suppliers[supplier.name] = supplier - - for part in self.parts: - supplier_part_id = part.order_supplier - - try: - supplier = SupplierPart.objects.get(pk=supplier_part_id).supplier - except SupplierPart.DoesNotExist: - continue - - if supplier.name not in suppliers: - supplier.order_items = [] - - # Attempt to auto-select a purchase order - orders = PurchaseOrder.objects.filter(supplier=supplier, status__in=PurchaseOrderStatus.OPEN) - - if orders.count() == 1: - supplier.selected_purchase_order = orders.first().id - else: - supplier.selected_purchase_order = None - - suppliers[supplier.name] = supplier - - suppliers[supplier.name].order_items.append(part) - - self.suppliers = [suppliers[key] for key in suppliers.keys()] - - def get_parts(self): - """ Determine which parts the user wishes to order. - This is performed on the initial GET request. - """ - - self.parts = [] - - part_ids = set() - - # User has passed a list of stock items - if 'stock[]' in self.request.GET: - - stock_id_list = self.request.GET.getlist('stock[]') - - """ Get a list of all the parts associated with the stock items. - - Base part must be purchaseable. - - Return a set of corresponding Part IDs - """ - stock_items = StockItem.objects.filter( - part__purchaseable=True, - id__in=stock_id_list) - - for item in stock_items: - part_ids.add(item.part.id) - - # User has passed a single Part ID - elif 'part' in self.request.GET: - try: - part_id = self.request.GET.get('part') - part = Part.objects.get(id=part_id) - - part_ids.add(part.id) - - except Part.DoesNotExist: - pass - - # User has passed a list of part ID values - elif 'parts[]' in self.request.GET: - part_id_list = self.request.GET.getlist('parts[]') - - parts = Part.objects.filter( - purchaseable=True, - id__in=part_id_list) - - for part in parts: - part_ids.add(part.id) - - # User has provided a Build ID - elif 'build' in self.request.GET: - build_id = self.request.GET.get('build') - try: - build = Build.objects.get(id=build_id) - - parts = build.required_parts - - for part in parts: - - # If ordering from a Build page, ignore parts that we have enough of - if part.quantity_to_order <= 0: - continue - part_ids.add(part.id) - except Build.DoesNotExist: - pass - - # Create the list of parts - for id in part_ids: - try: - part = Part.objects.get(id=id) - # Pre-fill the 'order quantity' value - part.order_quantity = part.quantity_to_order - - default_supplier = part.get_default_supplier() - - if default_supplier: - part.order_supplier = default_supplier.id - else: - part.order_supplier = None - except Part.DoesNotExist: - continue - - self.parts.append(part) - - def get(self, request, *args, **kwargs): - - self.request = request - - self.get_parts() - - return self.renderJsonResponse(request) - - def post(self, request, *args, **kwargs): - """ Handle the POST action for part selection. - - - Validates each part / quantity / supplier / etc - - Part selection form contains the following fields for each part: - - - supplier- : The ID of the selected supplier - - quantity- : The quantity to add to the order - """ - - self.request = request - - self.parts = [] - self.suppliers = [] - - # Any errors for the part selection form? - part_errors = False - supplier_errors = False - - # Extract part information from the form - for item in self.request.POST: - - if item.startswith('part-supplier-'): - - pk = item.replace('part-supplier-', '') - - # Check that the part actually exists - try: - part = Part.objects.get(id=pk) - except (Part.DoesNotExist, ValueError): - continue - - supplier_part_id = self.request.POST[item] - - quantity = self.request.POST.get('part-quantity-' + str(pk), 0) - - # Ensure a valid supplier has been passed - try: - supplier_part = SupplierPart.objects.get(id=supplier_part_id) - except (SupplierPart.DoesNotExist, ValueError): - supplier_part = None - - # Ensure a valid quantity is passed - try: - quantity = int(quantity) - - # Eliminate lines where the quantity is zero - if quantity == 0: - continue - except ValueError: - quantity = part.quantity_to_order - - part.order_supplier = supplier_part.id if supplier_part else None - part.order_quantity = quantity - - # set supplier-price - if supplier_part: - supplier_price = supplier_part.get_price(quantity) - if supplier_price: - part.purchase_price = supplier_price / quantity - if not hasattr(part, 'purchase_price'): - part.purchase_price = None - - self.parts.append(part) - - if supplier_part is None: - part_errors = True - - elif quantity < 0: - part_errors = True - - elif item.startswith('purchase-order-'): - # Which purchase order is selected for a given supplier? - pk = item.replace('purchase-order-', '') - - # Check that the Supplier actually exists - try: - supplier = Company.objects.get(id=pk) - except Company.DoesNotExist: - # Skip this item - continue - - purchase_order_id = self.request.POST[item] - - # Ensure that a valid purchase order has been passed - try: - purchase_order = PurchaseOrder.objects.get(pk=purchase_order_id) - except (PurchaseOrder.DoesNotExist, ValueError): - purchase_order = None - - supplier.selected_purchase_order = purchase_order.id if purchase_order else None - - self.suppliers.append(supplier) - - if supplier.selected_purchase_order is None: - supplier_errors = True - - form_step = request.POST.get('form_step') - - # Map parts to suppliers - self.get_suppliers() - - valid = False - - if form_step == 'select_parts': - # No errors? and the price-update button was not used to submit? Proceed to PO selection form - if part_errors is False and 'act-btn_update_price' not in request.POST: - self.ajax_template_name = 'order/order_wizard/select_pos.html' - self.form_step = 'select_purchase_orders' # set step (important for get_data) - - else: - self.ajax_template_name = 'order/order_wizard/select_parts.html' - - elif form_step == 'select_purchase_orders': - - self.ajax_template_name = 'order/order_wizard/select_pos.html' - - valid = part_errors is False and supplier_errors is False - - # Form wizard is complete! Add items to purchase orders - if valid: - self.order_items() - - data = { - 'form_valid': valid, - 'success': _('Ordered {n} parts').format(n=len(self.parts)) - } - - return self.renderJsonResponse(self.request, data=data) - - @transaction.atomic - def order_items(self): - """ Add the selected items to the purchase orders. """ - - for supplier in self.suppliers: - - # Check that the purchase order does actually exist - try: - order = PurchaseOrder.objects.get(pk=supplier.selected_purchase_order) - except PurchaseOrder.DoesNotExist: - logger.critical('Could not add items to purchase order {po} - Order does not exist'.format(po=supplier.selected_purchase_order)) - continue - - for item in supplier.order_items: - - # Ensure that the quantity is valid - try: - quantity = int(item.order_quantity) - if quantity <= 0: - continue - except ValueError: - logger.warning("Did not add part to purchase order - incorrect quantity") - continue - - # Check that the supplier part does actually exist - try: - supplier_part = SupplierPart.objects.get(pk=item.order_supplier) - except SupplierPart.DoesNotExist: - logger.critical("Could not add part '{part}' to purchase order - selected supplier part '{sp}' does not exist.".format( - part=item, - sp=item.order_supplier)) - continue - - # get purchase price - purchase_price = item.purchase_price - - order.add_line_item(supplier_part, quantity, purchase_price=purchase_price) - - class LineItemPricing(PartPricing): """ View for inspecting part pricing information """ diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js index c9ebbe0e22..d68b319a25 100644 --- a/InvenTree/templates/js/translated/build.js +++ b/InvenTree/templates/js/translated/build.js @@ -1532,13 +1532,18 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) { var pk = $(this).attr('pk'); - launchModalForm('{% url "order-parts" %}', { - data: { - parts: [ - pk, - ] + inventreeGet( + `/api/part/${pk}/`, + {}, + { + success: function(part) { + orderParts( + [part], + {} + ); + } } - }); + ); }); // Callback for 'build' button diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 496ea6a61e..d5ca7caf42 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -3290,13 +3290,18 @@ function loadSalesOrderLineItemTable(table, options={}) { $(table).find('.button-buy').click(function() { var pk = $(this).attr('pk'); - launchModalForm('{% url "order-parts" %}', { - data: { - parts: [ - pk - ], - }, - }); + inventreeGet( + `/api/part/${pk}/`, + {}, + { + success: function(part) { + orderParts( + [part], + {} + ); + } + } + ); }); // Callback for displaying price diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index de42528142..94d21fe5b0 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -2043,17 +2043,17 @@ function loadStockTable(table, options) { $('#multi-item-order').click(function() { var selections = $(table).bootstrapTable('getSelections'); - var stock = []; + var parts = []; selections.forEach(function(item) { - stock.push(item.pk); + var part = item.part_detail; + + if (part) { + parts.push(part); + } }); - launchModalForm('/order/purchase-order/order-parts/', { - data: { - stock: stock, - }, - }); + orderParts(parts, {}); }); $('#multi-item-set-status').click(function() {