Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2022-05-03 20:59:28 +10:00
commit 0de2d8a3ce
14 changed files with 151 additions and 602 deletions

View File

@ -546,14 +546,6 @@ $('#allocate-selected-items').click(function() {
); );
}); });
$("#btn-order-parts").click(function() {
launchModalForm("/order/purchase-order/order-parts/", {
data: {
build: {{ build.id }},
},
});
});
{% endif %} {% endif %}
enableSidebar('buildorder'); enableSidebar('buildorder');

View File

@ -325,14 +325,14 @@
var parts = []; var parts = [];
selections.forEach(function(item) { selections.forEach(function(item) {
parts.push(item.part); var part = item.part_detail;
part.manufacturer_part = item.pk;
parts.push(part);
}); });
launchModalForm("/order/purchase-order/order-parts/", { orderParts(
data: { parts,
parts: parts, );
},
});
}); });
{% endif %} {% endif %}
@ -396,14 +396,16 @@
var parts = []; var parts = [];
selections.forEach(function(item) { selections.forEach(function(item) {
parts.push(item.part); var part = item.part_detail;
parts.push(part);
}); });
launchModalForm("/order/purchase-order/order-parts/", { orderParts(
data: { parts,
parts: parts, {
}, supplier: {{ company.pk }},
}); }
);
}); });
{% endif %} {% endif %}

View File

@ -31,13 +31,11 @@
{% include "admin_button.html" with url=url %} {% include "admin_button.html" with url=url %}
{% endif %} {% endif %}
{% if roles.purchase_order.change %} {% if roles.purchase_order.change %}
{% comment "for later" %} {% if roles.purchase_order.add and part.part.purchaseable %}
{% if roles.purchase_order.add %}
<button type='button' class='btn btn-outline-secondary' id='order-part' title='{% trans "Order part" %}'> <button type='button' class='btn btn-outline-secondary' id='order-part' title='{% trans "Order part" %}'>
<span class='fas fa-shopping-cart'></span> <span class='fas fa-shopping-cart'></span>
</button> </button>
{% endif %} {% endif %}
{% endcomment %}
<button type='button' class='btn btn-outline-secondary' id='edit-part' title='{% trans "Edit manufacturer part" %}'> <button type='button' class='btn btn-outline-secondary' id='edit-part' title='{% trans "Edit manufacturer part" %}'>
<span class='fas fa-edit icon-green'/> <span class='fas fa-edit icon-green'/>
</button> </button>
@ -130,6 +128,7 @@ src="{% static 'img/blank_image.png' %}"
<li><a class='dropdown-item' href='#' id='supplier-part-delete' title='{% trans "Delete supplier parts" %}'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete" %}</a></li> <li><a class='dropdown-item' href='#' id='supplier-part-delete' title='{% trans "Delete supplier parts" %}'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete" %}</a></li>
</ul> </ul>
</div> </div>
{% include "filter_list.html" with id='supplier-part' %}
</div> </div>
</div> </div>
@ -300,14 +299,20 @@ linkButtonsToSelection($("#supplier-table"), ['#supplier-part-options']);
linkButtonsToSelection($("#parameter-table"), ['#parameter-options']); linkButtonsToSelection($("#parameter-table"), ['#parameter-options']);
$('#order-part, #order-part2').click(function() { $('#order-part, #order-part2').click(function() {
launchModalForm(
"{% url 'order-parts' %}", inventreeGet(
'{% url "api-part-detail" part.part.pk %}', {},
{ {
data: { success: function(response) {
part: {{ part.part.id }},
}, orderParts([response], {
reload: true, manufacturer_part: {{ part.pk }},
}, {% if part.manufacturer %}
manufacturer: {{ part.manufacturer.pk }},
{% endif %}
});
}
}
); );
}); });

View File

@ -165,7 +165,8 @@ src="{% static 'img/blank_image.png' %}"
</div> </div>
<div class='panel-content'> <div class='panel-content'>
<div id='button-bar'> <div id='button-bar'>
<div class='btn-group'> <div class='btn-group' role='group'>
{% include "filter_list.html" with id='purchaseorder' %}
</div> </div>
</div> </div>
<table class='table table-striped table-condensed po-table' id='purchase-order-table' data-toolbar='#button-bar'> <table class='table table-striped table-condensed po-table' id='purchase-order-table' data-toolbar='#button-bar'>
@ -326,14 +327,19 @@ $("#item-create").click(function() {
}); });
$('#order-part, #order-part2').click(function() { $('#order-part, #order-part2').click(function() {
launchModalForm(
"{% url 'order-parts' %}", inventreeGet(
'{% url "api-part-detail" part.part.pk %}', {},
{ {
data: { success: function(response) {
part: {{ part.part.id }}, orderParts([response], {
}, supplier_part: {{ part.pk }},
reload: true, {% if part.supplier %}
}, supplier: {{ part.supplier.pk }},
{% endif %}
});
}
}
); );
}); });

View File

@ -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 %}
<h4>
{% trans "Step 1 of 2 - Select Part Suppliers" %}
</h4>
{% if parts|length > 0 %}
<div class='alert alert-info alert-block' role='alert'>
{% trans "Select suppliers" %}
</div>
{% else %}
<div class='alert alert-warning alert-block' role='alert'>
{% trans "No purchaseable parts selected" %}
</div>
{% endif %}
<form method="post" action='' class='js-modal-form' enctype="multipart/form-data">
{% csrf_token %}
{% load crispy_forms_tags %}
<input type='hidden' name='form_step' value='select_parts'/>
<table class='table table-condensed table-striped' id='order-wizard-part-table'>
<tr>
<th>{% trans "Part" %}</th>
<th colspan='2'>{% trans "Select Supplier" %}</th>
<th>{% trans "Quantity" %}</th>
<th></th>
</tr>
{% for part in parts %}
<tr id='part_row_{{ part.id }}'>
<td>
{% include "hover_image.html" with image=part.image hover=False %}
{{ part.full_name }} <small><em>{{ part.description }}</em></small>
</td>
<td>
<button class='btn btn-outline-secondary btn-create' onClick='newSupplierPartFromOrderWizard()' id='new_supplier_part_{{ part.id }}' part='{{ part.pk }}' title='{% trans "Create new supplier part" %}' type='button'>
<span part='{{ part.pk }}' class='fas fa-plus-circle'></span>
</button>
</td>
<td>
<div class='control-group'>
<div class='controls'>
<select class='select' id='id_supplier_part_{{ part.id }}' name="part-supplier-{{ part.id }}">
<option value=''>---------</option>
{% for supplier in part.supplier_parts.all %}
<option value="{{ supplier.id }}"{% if part.order_supplier == supplier.id %} selected="selected"{% endif %}>
{% if show_price %}
{% call_method supplier 'get_price' part.order_quantity as price %}
{% if price != None %}{% include "price.html" with price=price %}{% else %}{% trans 'No price' %}{% endif %} -
{% endif %}
{{ supplier }}
</option>
{% endfor %}
</select>
</div>
{% if not part.order_supplier %}
<span class='help-inline'>{% blocktrans with name=part.name %}Select a supplier for <em>{{name}}</em>{% endblocktrans %}</span>
{% endif %}
</div>
</td>
<td>
<div class='control-group'>
<div class='controls'>
<input class='numberinput' type='number' min='0' value='{% decimal part.order_quantity %}' name='part-quantity-{{ part.id }}'/>
</div>
</div>
</td>
<td>
<button class='btn btn-outline-secondary btn-remove' onclick='removeOrderRowFromOrderWizard()' id='del_item_{{ part.id }}' title='{% trans "Remove part" %}' type='button'>
<span row='part_row_{{ part.id }}' class='fas fa-trash-alt icon-red'></span>
</button>
</td>
</tr>
{% endfor %}
</table>
</form>
{% endblock %}

View File

@ -1,77 +0,0 @@
{% extends "modal_form.html" %}
{% load i18n %}
{% block form %}
<h4>
{% trans "Step 2 of 2 - Select Purchase Orders" %}
</h4>
<div class='alert alert-info alert-block' role='alert'>
{% trans "Select existing purchase orders, or create new orders." %}
</div>
<form method='post' action='' class='js-modal-form' enctype='multipart/form-data'>
{% csrf_token %}
{% load crispy_forms_tags %}
<input type='hidden' name='form_step' value='select_purchase_orders'/>
{% for supplier in suppliers %}
{% for item in supplier.order_items %}
<input type='hidden' name='part-supplier-{{ item.id }}' value='{{ item.order_supplier }}'/>
<input type='hidden' name='part-quantity-{{ item.id }}' value='{{ item.order_quantity }}'/>
{% endfor %}
{% endfor %}
<table class='table table-condensed table-striped' id='order-wizard-po-table'>
<tr>
<th>{% trans "Supplier" %}</th>
<th>{% trans "Items" %}</th>
<th colspan='2'>{% trans "Select Purchase Order" %}</th>
</tr>
{% for supplier in suppliers %}
<tr id='suppier_row_{{ supplier.id }}'>
<td>
{% include 'hover_image.html' with image=supplier.image hover=False %}
{{ supplier.name }}
</td>
<td>{{ supplier.order_items|length }}</td>
<td>
<button
class='btn btn-outline-secondary btn-create'
id='new_po_{{ supplier.id }}'
title='{% blocktrans with name=supplier.name %}Create new purchase order for {{name}}{% endblocktrans %}'
type='button'
supplierid='{{ supplier.id }}'
onclick='newPurchaseOrderFromOrderWizard()'>
<span supplierid='{{ supplier.id }}' class='fas fa-plus-circle'></span>
</button>
</td>
<td>
<div class='control-group'>
<div class='controls'>
<select
class='select'
id='id-purchase-order-{{ supplier.id }}'
name='purchase-order-{{ supplier.id }}'>
<option value=''>---------</option>
{% for order in supplier.pending_purchase_orders %}
<option value='{{ order.id }}'{% if supplier.selected_purchase_order == order.id %} selected='selected'{% endif %}>
{{ order }}
</option>
{% endfor %}
</select>
</div>
{% if not supplier.selected_purchase_order %}
<span class='help-inline'>{% blocktrans with name=supplier.name %}Select a purchase order for {{name}}{% endblocktrans %}</span>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</table>
</form>
{% endblock %}

View File

@ -23,7 +23,6 @@ purchase_order_detail_urls = [
purchase_order_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'), re_path(r'^pricing/', views.LineItemPricing.as_view(), name='line-pricing'),
# Display detail view for a single purchase order # Display detail view for a single purchase order

View File

@ -5,7 +5,6 @@ Django views for interacting with Order app
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import transaction
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from django.http.response import JsonResponse from django.http.response import JsonResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
@ -21,9 +20,7 @@ from decimal import Decimal, InvalidOperation
from .models import PurchaseOrder, PurchaseOrderLineItem from .models import PurchaseOrder, PurchaseOrderLineItem
from .models import SalesOrder, SalesOrderLineItem from .models import SalesOrder, SalesOrderLineItem
from .admin import PurchaseOrderLineItemResource, SalesOrderLineItemResource from .admin import PurchaseOrderLineItemResource, SalesOrderLineItemResource
from build.models import Build from company.models import SupplierPart # ManufacturerPart
from company.models import Company, SupplierPart # ManufacturerPart
from stock.models import StockItem
from part.models import Part from part.models import Part
from common.forms import UploadFileForm, MatchFieldForm from common.forms import UploadFileForm, MatchFieldForm
@ -37,8 +34,6 @@ from InvenTree.views import AjaxView, AjaxUpdateView
from InvenTree.helpers import DownloadFile, str2bool from InvenTree.helpers import DownloadFile, str2bool
from InvenTree.views import InvenTreeRoleMixin from InvenTree.views import InvenTreeRoleMixin
from InvenTree.status_codes import PurchaseOrderStatus
logger = logging.getLogger("inventree") logger = logging.getLogger("inventree")
@ -448,346 +443,6 @@ class PurchaseOrderExport(AjaxView):
return DownloadFile(filedata, filename) 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-<pk> : The ID of the selected supplier
- quantity-<pk> : 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): class LineItemPricing(PartPricing):
""" View for inspecting part pricing information """ """ View for inspecting part pricing information """

View File

@ -754,12 +754,18 @@
$("#part-order2").click(function() { $("#part-order2").click(function() {
launchModalForm("{% url 'order-parts' %}", { inventreeGet(
data: { '{% url "api-part-detail" part.pk %}',
part: {{ part.id }}, {},
}, {
reload: true, success: function(part) {
}); orderParts(
[part],
{}
);
}
}
);
}); });
onPanelLoad("test-templates", function() { onPanelLoad("test-templates", function() {

View File

@ -549,15 +549,6 @@
} }
} }
); );
return;
launchModalForm("{% url 'order-parts' %}", {
data: {
part: {{ part.id }},
},
reload: true,
});
}); });
{% if roles.part.add %} {% if roles.part.add %}

View File

@ -163,17 +163,15 @@ def after_save(sender, instance, created, **kwargs):
if created: if created:
trigger_event( trigger_event(
'instance.created', f'{table}.created',
id=instance.id, id=instance.id,
model=sender.__name__, model=sender.__name__,
table=table,
) )
else: else:
trigger_event( trigger_event(
'instance.saved', f'{table}.saved',
id=instance.id, id=instance.id,
model=sender.__name__, model=sender.__name__,
table=table,
) )
@ -189,9 +187,8 @@ def after_delete(sender, instance, **kwargs):
return return
trigger_event( trigger_event(
'instance.deleted', f'{table}.deleted',
model=sender.__name__, model=sender.__name__,
table=table,
) )

View File

@ -1532,13 +1532,18 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
var pk = $(this).attr('pk'); var pk = $(this).attr('pk');
launchModalForm('{% url "order-parts" %}', { inventreeGet(
data: { `/api/part/${pk}/`,
parts: [ {},
pk, {
] success: function(part) {
orderParts(
[part],
{}
);
}
} }
}); );
}); });
// Callback for 'build' button // Callback for 'build' button

View File

@ -485,9 +485,16 @@ function orderParts(parts_list, options={}) {
var parts = []; var parts = [];
var parts_seen = {};
parts_list.forEach(function(part) { parts_list.forEach(function(part) {
if (part.purchaseable) { if (part.purchaseable) {
parts.push(part);
// Prevent duplicates
if (!(part.pk in parts_seen)) {
parts_seen[part.pk] = true;
parts.push(part);
}
} }
}); });
@ -596,6 +603,17 @@ function orderParts(parts_list, options={}) {
return html; return html;
} }
// Remove a single row form this dialog
function removeRow(pk, opts) {
// Remove the row
$(opts.modal).find(`#order_row_${pk}`).remove();
// If the modal is now "empty", dismiss it
if (!($(opts.modal).find('.part-order-row').exists())) {
closeModal(opts.modal);
}
}
var table_entries = ''; var table_entries = '';
parts.forEach(function(part) { parts.forEach(function(part) {
@ -622,14 +640,50 @@ function orderParts(parts_list, options={}) {
</table> </table>
`; `;
// Construct API filters for the SupplierPart field
var supplier_part_filters = {
supplier_detail: true,
part_detail: true,
};
if (options.supplier) {
supplier_part_filters.supplier = options.supplier;
}
if (options.manufacturer) {
supplier_part_filters.manufacturer = options.manufacturer;
}
if (options.manufacturer_part) {
supplier_part_filters.manufacturer_part = options.manufacturer_part;
}
// Construct API filtres for the PurchaseOrder field
var order_filters = {
status: {{ PurchaseOrderStatus.PENDING }},
supplier_detail: true,
};
if (options.supplier) {
order_filters.supplier = options.supplier;
}
constructFormBody({}, { constructFormBody({}, {
preFormContent: html, preFormContent: html,
title: '{% trans "Order Parts" %}', title: '{% trans "Order Parts" %}',
preventSubmit: true, preventSubmit: true,
closeText: '{% trans "Close" %}', closeText: '{% trans "Close" %}',
afterRender: function(fields, opts) { afterRender: function(fields, opts) {
// TODO
parts.forEach(function(part) { parts.forEach(function(part) {
// Filter by base part
supplier_part_filters.part = part.pk;
if (part.manufacturer_part) {
// Filter by manufacturer part
supplier_part_filters.manufacturer_part = part.manufacturer_part;
}
// Configure the "supplier part" field // Configure the "supplier part" field
initializeRelatedField({ initializeRelatedField({
name: `part_${part.pk}`, name: `part_${part.pk}`,
@ -638,11 +692,8 @@ function orderParts(parts_list, options={}) {
required: true, required: true,
type: 'related field', type: 'related field',
auto_fill: true, auto_fill: true,
filters: { value: options.supplier_part,
part: part.pk, filters: supplier_part_filters,
supplier_detail: true,
part_detail: false,
},
noResults: function(query) { noResults: function(query) {
return '{% trans "No matching supplier parts" %}'; return '{% trans "No matching supplier parts" %}';
} }
@ -656,10 +707,8 @@ function orderParts(parts_list, options={}) {
required: true, required: true,
type: 'related field', type: 'related field',
auto_fill: false, auto_fill: false,
filters: { value: options.order,
status: {{ PurchaseOrderStatus.PENDING }}, filters: order_filters,
supplier_detail: true,
},
noResults: function(query) { noResults: function(query) {
return '{% trans "No matching purchase orders" %}'; return '{% trans "No matching purchase orders" %}';
} }
@ -689,8 +738,7 @@ function orderParts(parts_list, options={}) {
{ {
method: 'POST', method: 'POST',
success: function(response) { success: function(response) {
// Remove the row removeRow(pk, opts);
$(opts.modal).find(`#order_row_${pk}`).remove();
}, },
error: function(xhr) { error: function(xhr) {
switch (xhr.status) { switch (xhr.status) {
@ -711,7 +759,7 @@ function orderParts(parts_list, options={}) {
$(opts.modal).find('.button-row-remove').click(function() { $(opts.modal).find('.button-row-remove').click(function() {
var pk = $(this).attr('pk'); var pk = $(this).attr('pk');
$(opts.modal).find(`#order_row_${pk}`).remove(); removeRow(pk, opts);
}); });
// Add callback for "new supplier part" button // Add callback for "new supplier part" button
@ -3242,13 +3290,18 @@ function loadSalesOrderLineItemTable(table, options={}) {
$(table).find('.button-buy').click(function() { $(table).find('.button-buy').click(function() {
var pk = $(this).attr('pk'); var pk = $(this).attr('pk');
launchModalForm('{% url "order-parts" %}', { inventreeGet(
data: { `/api/part/${pk}/`,
parts: [ {},
pk {
], success: function(part) {
}, orderParts(
}); [part],
{}
);
}
}
);
}); });
// Callback for displaying price // Callback for displaying price

View File

@ -2043,17 +2043,17 @@ function loadStockTable(table, options) {
$('#multi-item-order').click(function() { $('#multi-item-order').click(function() {
var selections = $(table).bootstrapTable('getSelections'); var selections = $(table).bootstrapTable('getSelections');
var stock = []; var parts = [];
selections.forEach(function(item) { selections.forEach(function(item) {
stock.push(item.pk); var part = item.part_detail;
if (part) {
parts.push(part);
}
}); });
launchModalForm('/order/purchase-order/order-parts/', { orderParts(parts, {});
data: {
stock: stock,
},
});
}); });
$('#multi-item-set-status').click(function() { $('#multi-item-set-status').click(function() {