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 %}
-
- {% trans "Select suppliers" %}
-
-{% else %}
-
- {% trans "No purchaseable parts selected" %}
-
-{% 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" %}
-
-
-
- {% trans "Select existing purchase orders, or create new orders." %}
-
-
-
-{% 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() {