mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-01 19:20:55 +00:00
Merge remote-tracking branch 'inventree/master' into order-parts-wizard
This commit is contained in:
@ -179,6 +179,11 @@ function showApiError(xhr, url) {
|
||||
var title = null;
|
||||
var message = null;
|
||||
|
||||
if (xhr.statusText == 'abort') {
|
||||
// Don't show errors for requests which were intentionally aborted
|
||||
return;
|
||||
}
|
||||
|
||||
switch (xhr.status || 0) {
|
||||
// No response
|
||||
case 0:
|
||||
|
@ -359,14 +359,13 @@ function unlinkBarcode(stockitem) {
|
||||
/*
|
||||
* Display dialog to check multiple stock items in to a stock location.
|
||||
*/
|
||||
function barcodeCheckIn(location_id) {
|
||||
function barcodeCheckIn(location_id, options={}) {
|
||||
|
||||
var modal = '#modal-form';
|
||||
|
||||
// List of items we are going to checkin
|
||||
var items = [];
|
||||
|
||||
|
||||
function reloadTable() {
|
||||
|
||||
modalEnable(modal, false);
|
||||
@ -389,10 +388,17 @@ function barcodeCheckIn(location_id) {
|
||||
<tbody>`;
|
||||
|
||||
items.forEach(function(item) {
|
||||
|
||||
var location_info = `${item.location}`;
|
||||
|
||||
if (item.location_detail) {
|
||||
location_info = `${item.location_detail.name}`;
|
||||
}
|
||||
|
||||
html += `
|
||||
<tr pk='${item.pk}'>
|
||||
<td>${imageHoverIcon(item.part_detail.thumbnail)} ${item.part_detail.name}</td>
|
||||
<td>${item.location_detail.name}</td>
|
||||
<td>${location_info}</td>
|
||||
<td>${item.quantity}</td>
|
||||
<td>${makeIconButton('fa-times-circle icon-red', 'button-item-remove', item.pk, '{% trans "Remove stock item" %}')}</td>
|
||||
</tr>`;
|
||||
@ -469,6 +475,12 @@ function barcodeCheckIn(location_id) {
|
||||
|
||||
data.items = entries;
|
||||
|
||||
// Prevent submission without any entries
|
||||
if (entries.length == 0) {
|
||||
showBarcodeMessage(modal, '{% trans "No barcode provided" %}', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
inventreePut(
|
||||
'{% url "api-stock-transfer" %}',
|
||||
data,
|
||||
@ -477,15 +489,11 @@ function barcodeCheckIn(location_id) {
|
||||
success: function(response, status) {
|
||||
// Hide the modal
|
||||
$(modal).modal('hide');
|
||||
if (status == 'success' && 'success' in response) {
|
||||
|
||||
addCachedAlert(response.success);
|
||||
location.reload();
|
||||
if (options.success) {
|
||||
options.success(response);
|
||||
} else {
|
||||
showMessage('{% trans "Error transferring stock" %}', {
|
||||
style: 'danger',
|
||||
icon: 'fas fa-times-circle',
|
||||
});
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -533,7 +541,7 @@ function barcodeCheckIn(location_id) {
|
||||
/*
|
||||
* Display dialog to check a single stock item into a stock location
|
||||
*/
|
||||
function scanItemsIntoLocation(item_id_list, options={}) {
|
||||
function scanItemsIntoLocation(item_list, options={}) {
|
||||
|
||||
var modal = options.modal || '#modal-form';
|
||||
|
||||
@ -583,9 +591,10 @@ function scanItemsIntoLocation(item_id_list, options={}) {
|
||||
|
||||
var items = [];
|
||||
|
||||
item_id_list.forEach(function(pk) {
|
||||
item_list.forEach(function(item) {
|
||||
items.push({
|
||||
pk: pk,
|
||||
pk: item.pk || item.id,
|
||||
quantity: item.quantity,
|
||||
});
|
||||
});
|
||||
|
||||
@ -605,13 +614,10 @@ function scanItemsIntoLocation(item_id_list, options={}) {
|
||||
// First hide the modal
|
||||
$(modal).modal('hide');
|
||||
|
||||
if (status == 'success' && 'success' in response) {
|
||||
addCachedAlert(response.success);
|
||||
location.reload();
|
||||
if (options.success) {
|
||||
options.success(response);
|
||||
} else {
|
||||
showMessage('{% trans "Error transferring stock" %}', {
|
||||
style: 'danger',
|
||||
});
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1025,9 +1025,10 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
}
|
||||
|
||||
// Store the required quantity in the row data
|
||||
row.required = quantity;
|
||||
// Prevent weird rounding issues
|
||||
row.required = parseFloat(quantity.toFixed(15));
|
||||
|
||||
return quantity;
|
||||
return row.required;
|
||||
}
|
||||
|
||||
function sumAllocations(row) {
|
||||
@ -1043,9 +1044,9 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
quantity += item.quantity;
|
||||
});
|
||||
|
||||
row.allocated = quantity;
|
||||
row.allocated = parseFloat(quantity.toFixed(15));
|
||||
|
||||
return quantity;
|
||||
return row.allocated;
|
||||
}
|
||||
|
||||
function setupCallbacks() {
|
||||
@ -1642,6 +1643,9 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
||||
remaining = 0;
|
||||
}
|
||||
|
||||
// Ensure the quantity sent to the form field is correctly formatted
|
||||
remaining = parseFloat(remaining.toFixed(15));
|
||||
|
||||
// We only care about entries which are not yet fully allocated
|
||||
if (remaining > 0) {
|
||||
table_entries += renderBomItemRow(bom_item, remaining);
|
||||
@ -1742,7 +1746,7 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
||||
required: true,
|
||||
render_part_detail: true,
|
||||
render_location_detail: true,
|
||||
render_stock_id: false,
|
||||
render_pk: false,
|
||||
auto_fill: true,
|
||||
auto_fill_filters: auto_fill_filters,
|
||||
onSelect: function(data, field, opts) {
|
||||
|
@ -10,6 +10,7 @@
|
||||
modalSetTitle,
|
||||
modalSubmit,
|
||||
openModal,
|
||||
plugins_enabled,
|
||||
showAlertDialog,
|
||||
*/
|
||||
|
||||
@ -232,26 +233,28 @@ function selectLabel(labels, items, options={}) {
|
||||
var plugins = [];
|
||||
|
||||
// Request a list of available label printing plugins from the server
|
||||
inventreeGet(
|
||||
`/api/plugin/`,
|
||||
{},
|
||||
{
|
||||
async: false,
|
||||
success: function(response) {
|
||||
response.forEach(function(plugin) {
|
||||
// Look for active plugins which implement the 'labels' mixin class
|
||||
if (plugin.active && plugin.mixins && plugin.mixins.labels) {
|
||||
// This plugin supports label printing
|
||||
plugins.push(plugin);
|
||||
}
|
||||
});
|
||||
if (plugins_enabled) {
|
||||
inventreeGet(
|
||||
`/api/plugin/`,
|
||||
{},
|
||||
{
|
||||
async: false,
|
||||
success: function(response) {
|
||||
response.forEach(function(plugin) {
|
||||
// Look for active plugins which implement the 'labels' mixin class
|
||||
if (plugin.active && plugin.mixins && plugin.mixins.labels) {
|
||||
// This plugin supports label printing
|
||||
plugins.push(plugin);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
var plugin_selection = '';
|
||||
|
||||
if (plugins.length > 0) {
|
||||
if (plugins_enabled && plugins.length > 0) {
|
||||
plugin_selection =`
|
||||
<div class='form-group'>
|
||||
<label class='control-label requiredField' for='id_plugin'>
|
||||
@ -263,7 +266,7 @@ function selectLabel(labels, items, options={}) {
|
||||
`;
|
||||
|
||||
plugins.forEach(function(plugin) {
|
||||
plugin_selection += `<option value='${plugin.key}' title='${plugin.meta.human_name}'>${plugin.meta.description} - <small>${plugin.meta.human_name}</small></option>`;
|
||||
plugin_selection += `<option value='${plugin.key}' title='${plugin.meta.human_name}'>${plugin.name} - <small>${plugin.meta.human_name}</small></option>`;
|
||||
});
|
||||
|
||||
plugin_selection += `
|
||||
|
@ -10,7 +10,9 @@
|
||||
renderCompany,
|
||||
renderManufacturerPart,
|
||||
renderOwner,
|
||||
renderPart,
|
||||
renderPartCategory,
|
||||
renderStockItem,
|
||||
renderStockLocation,
|
||||
renderSupplierPart,
|
||||
*/
|
||||
@ -29,15 +31,33 @@
|
||||
*/
|
||||
|
||||
|
||||
// Should the ID be rendered for this string
|
||||
function renderId(title, pk, parameters={}) {
|
||||
|
||||
// Default = true
|
||||
var render = true;
|
||||
|
||||
if ('render_pk' in parameters) {
|
||||
render = parameters['render_pk'];
|
||||
}
|
||||
|
||||
if (render) {
|
||||
return `<span class='float-right'><small>${title}: ${pk}</small></span>`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Renderer for "Company" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderCompany(name, data, parameters, options) {
|
||||
function renderCompany(name, data, parameters={}, options={}) {
|
||||
|
||||
var html = select2Thumbnail(data.image);
|
||||
|
||||
html += `<span><b>${data.name}</b></span> - <i>${data.description}</i>`;
|
||||
|
||||
html += `<span class='float-right'><small>{% trans "Company ID" %}: ${data.pk}</small></span>`;
|
||||
html += renderId('{% trans "Company ID" %}', data.pk, parameters);
|
||||
|
||||
return html;
|
||||
}
|
||||
@ -45,7 +65,7 @@ function renderCompany(name, data, parameters, options) {
|
||||
|
||||
// Renderer for "StockItem" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderStockItem(name, data, parameters, options) {
|
||||
function renderStockItem(name, data, parameters={}, options={}) {
|
||||
|
||||
var image = blankImage();
|
||||
|
||||
@ -65,18 +85,6 @@ function renderStockItem(name, data, parameters, options) {
|
||||
part_detail = `<img src='${image}' class='select2-thumbnail'><span>${data.part_detail.full_name}</span> - `;
|
||||
}
|
||||
|
||||
var render_stock_id = true;
|
||||
|
||||
if ('render_stock_id' in parameters) {
|
||||
render_stock_id = parameters['render_stock_id'];
|
||||
}
|
||||
|
||||
var stock_id = '';
|
||||
|
||||
if (render_stock_id) {
|
||||
stock_id = `<span class='float-right'><small>{% trans "Stock ID" %}: ${data.pk}</small></span>`;
|
||||
}
|
||||
|
||||
var render_location_detail = false;
|
||||
|
||||
if ('render_location_detail' in parameters) {
|
||||
@ -86,7 +94,7 @@ function renderStockItem(name, data, parameters, options) {
|
||||
var location_detail = '';
|
||||
|
||||
if (render_location_detail && data.location_detail) {
|
||||
location_detail = ` - (<em>${data.location_detail.name}</em>)`;
|
||||
location_detail = ` <small>- (<em>${data.location_detail.name}</em>)</small>`;
|
||||
}
|
||||
|
||||
var stock_detail = '';
|
||||
@ -101,7 +109,10 @@ function renderStockItem(name, data, parameters, options) {
|
||||
|
||||
var html = `
|
||||
<span>
|
||||
${part_detail}${stock_detail}${location_detail}${stock_id}
|
||||
${part_detail}
|
||||
${stock_detail}
|
||||
${location_detail}
|
||||
${renderId('{% trans "Stock ID" %}', data.pk, parameters)}
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -111,7 +122,7 @@ function renderStockItem(name, data, parameters, options) {
|
||||
|
||||
// Renderer for "StockLocation" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderStockLocation(name, data, parameters, options) {
|
||||
function renderStockLocation(name, data, parameters={}, options={}) {
|
||||
|
||||
var level = '- '.repeat(data.level);
|
||||
|
||||
@ -133,7 +144,7 @@ function renderStockLocation(name, data, parameters, options) {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderBuild(name, data, parameters, options) {
|
||||
function renderBuild(name, data, parameters={}, options={}) {
|
||||
|
||||
var image = null;
|
||||
|
||||
@ -154,7 +165,7 @@ function renderBuild(name, data, parameters, options) {
|
||||
|
||||
// Renderer for "Part" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderPart(name, data, parameters, options) {
|
||||
function renderPart(name, data, parameters={}, options={}) {
|
||||
|
||||
var html = select2Thumbnail(data.image);
|
||||
|
||||
@ -164,13 +175,14 @@ function renderPart(name, data, parameters, options) {
|
||||
html += ` - <i><small>${data.description}</small></i>`;
|
||||
}
|
||||
|
||||
var extra = '';
|
||||
var stock_data = '';
|
||||
|
||||
// Display available part quantity
|
||||
if (user_settings.PART_SHOW_QUANTITY_IN_FORMS) {
|
||||
extra += partStockLabel(data);
|
||||
stock_data = partStockLabel(data);
|
||||
}
|
||||
|
||||
var extra = '';
|
||||
|
||||
if (!data.active) {
|
||||
extra += `<span class='badge badge-right rounded-pill bg-danger'>{% trans "Inactive" %}</span>`;
|
||||
}
|
||||
@ -178,8 +190,9 @@ function renderPart(name, data, parameters, options) {
|
||||
html += `
|
||||
<span class='float-right'>
|
||||
<small>
|
||||
${stock_data}
|
||||
${extra}
|
||||
{% trans "Part ID" %}: ${data.pk}
|
||||
${renderId('{% trans "Part ID" $}', data.pk, parameters)}
|
||||
</small>
|
||||
</span>`;
|
||||
|
||||
@ -188,7 +201,7 @@ function renderPart(name, data, parameters, options) {
|
||||
|
||||
// Renderer for "User" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderUser(name, data, parameters, options) {
|
||||
function renderUser(name, data, parameters={}, options={}) {
|
||||
|
||||
var html = `<span>${data.username}</span>`;
|
||||
|
||||
@ -202,7 +215,7 @@ function renderUser(name, data, parameters, options) {
|
||||
|
||||
// Renderer for "Owner" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderOwner(name, data, parameters, options) {
|
||||
function renderOwner(name, data, parameters={}, options={}) {
|
||||
|
||||
var html = `<span>${data.name}</span>`;
|
||||
|
||||
@ -223,15 +236,13 @@ function renderOwner(name, data, parameters, options) {
|
||||
|
||||
// Renderer for "PurchaseOrder" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderPurchaseOrder(name, data, parameters, options) {
|
||||
var html = '';
|
||||
function renderPurchaseOrder(name, data, parameters={}, options={}) {
|
||||
|
||||
var prefix = global_settings.PURCHASEORDER_REFERENCE_PREFIX;
|
||||
var html = `<span>${prefix}${data.reference}</span>`;
|
||||
|
||||
var thumbnail = null;
|
||||
|
||||
html += `<span>${prefix}${data.reference}</span>`;
|
||||
|
||||
if (data.supplier_detail) {
|
||||
thumbnail = data.supplier_detail.thumbnail || data.supplier_detail.image;
|
||||
|
||||
@ -243,13 +254,7 @@ function renderPurchaseOrder(name, data, parameters, options) {
|
||||
html += ` - <em>${data.description}</em>`;
|
||||
}
|
||||
|
||||
html += `
|
||||
<span class='float-right'>
|
||||
<small>
|
||||
{% trans "Order ID" %}: ${data.pk}
|
||||
</small>
|
||||
</span>
|
||||
`;
|
||||
html += renderId('{% trans "Order ID" %}', data.pk, parameters);
|
||||
|
||||
return html;
|
||||
}
|
||||
@ -257,19 +262,25 @@ function renderPurchaseOrder(name, data, parameters, options) {
|
||||
|
||||
// Renderer for "SalesOrder" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderSalesOrder(name, data, parameters, options) {
|
||||
var html = `<span>${data.reference}</span>`;
|
||||
function renderSalesOrder(name, data, parameters={}, options={}) {
|
||||
|
||||
var prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
|
||||
var html = `<span>${prefix}${data.reference}</span>`;
|
||||
|
||||
var thumbnail = null;
|
||||
|
||||
if (data.customer_detail) {
|
||||
thumbnail = data.customer_detail.thumbnail || data.customer_detail.image;
|
||||
|
||||
html += ' - ' + select2Thumbnail(thumbnail);
|
||||
html += `<span>${data.customer_detail.name}</span>`;
|
||||
}
|
||||
|
||||
if (data.description) {
|
||||
html += ` - <em>${data.description}</em>`;
|
||||
}
|
||||
|
||||
html += `
|
||||
<span class='float-right'>
|
||||
<small>
|
||||
{% trans "Order ID" %}: ${data.pk}
|
||||
</small>
|
||||
</span>`;
|
||||
html += renderId('{% trans "Order ID" %}', data.pk, parameters);
|
||||
|
||||
return html;
|
||||
}
|
||||
@ -277,7 +288,7 @@ function renderSalesOrder(name, data, parameters, options) {
|
||||
|
||||
// Renderer for "SalesOrderShipment" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderSalesOrderShipment(name, data, parameters, options) {
|
||||
function renderSalesOrderShipment(name, data, parameters={}, options={}) {
|
||||
|
||||
var so_prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
|
||||
|
||||
@ -294,7 +305,7 @@ function renderSalesOrderShipment(name, data, parameters, options) {
|
||||
|
||||
// Renderer for "PartCategory" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderPartCategory(name, data, parameters, options) {
|
||||
function renderPartCategory(name, data, parameters={}, options={}) {
|
||||
|
||||
var level = '- '.repeat(data.level);
|
||||
|
||||
@ -310,7 +321,7 @@ function renderPartCategory(name, data, parameters, options) {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderPartParameterTemplate(name, data, parameters, options) {
|
||||
function renderPartParameterTemplate(name, data, parameters={}, options={}) {
|
||||
|
||||
var units = '';
|
||||
|
||||
@ -326,7 +337,7 @@ function renderPartParameterTemplate(name, data, parameters, options) {
|
||||
|
||||
// Renderer for "ManufacturerPart" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderManufacturerPart(name, data, parameters, options) {
|
||||
function renderManufacturerPart(name, data, parameters={}, options={}) {
|
||||
|
||||
var manufacturer_image = null;
|
||||
var part_image = null;
|
||||
@ -355,7 +366,7 @@ function renderManufacturerPart(name, data, parameters, options) {
|
||||
|
||||
// Renderer for "SupplierPart" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderSupplierPart(name, data, parameters, options) {
|
||||
function renderSupplierPart(name, data, parameters={}, options={}) {
|
||||
|
||||
var supplier_image = null;
|
||||
var part_image = null;
|
||||
|
@ -491,13 +491,50 @@ function duplicateBom(part_id, options={}) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Construct a "badge" label showing stock information for this particular part
|
||||
*/
|
||||
function partStockLabel(part, options={}) {
|
||||
|
||||
if (part.in_stock) {
|
||||
return `<span class='badge rounded-pill bg-success ${options.classes}'>{% trans "Stock" %}: ${part.in_stock}</span>`;
|
||||
// There IS stock available for this part
|
||||
|
||||
// Is stock "low" (below the 'minimum_stock' quantity)?
|
||||
if ((part.minimum_stock > 0) && (part.minimum_stock > part.in_stock)) {
|
||||
return `<span class='badge rounded-pill bg-warning ${options.classes}'>{% trans "Low stock" %}: ${part.in_stock}${part.units}</span>`;
|
||||
} else if (part.unallocated_stock == 0) {
|
||||
if (part.ordering) {
|
||||
// There is no available stock, but stock is on order
|
||||
return `<span class='badge rounded-pill bg-info ${options.classes}'>{% trans "On Order" %}: ${part.ordering}${part.units}</span>`;
|
||||
} else if (part.building) {
|
||||
// There is no available stock, but stock is being built
|
||||
return `<span class='badge rounded-pill bg-info ${options.classes}'>{% trans "Building" %}: ${part.building}${part.units}</span>`;
|
||||
} else {
|
||||
// There is no available stock at all
|
||||
return `<span class='badge rounded-pill bg-warning ${options.classes}'>{% trans "No stock available" %}</span>`;
|
||||
}
|
||||
} else if (part.unallocated_stock < part.in_stock) {
|
||||
// Unallocated quanttiy is less than total quantity
|
||||
return `<span class='badge rounded-pill bg-success ${options.classes}'>{% trans "Available" %}: ${part.unallocated_stock}/${part.in_stock}${part.units}</span>`;
|
||||
} else {
|
||||
// Stock is completely available
|
||||
return `<span class='badge rounded-pill bg-success ${options.classes}'>{% trans "Available" %}: ${part.unallocated_stock}${part.units}</span>`;
|
||||
}
|
||||
} else {
|
||||
return `<span class='badge rounded-pill bg-danger ${options.classes}'>{% trans "No Stock" %}</span>`;
|
||||
// There IS NO stock available for this part
|
||||
|
||||
if (part.ordering) {
|
||||
// There is no stock, but stock is on order
|
||||
return `<span class='badge rounded-pill bg-info ${options.classes}'>{% trans "On Order" %}: ${part.ordering}${part.units}</span>`;
|
||||
} else if (part.building) {
|
||||
// There is no stock, but stock is being built
|
||||
return `<span class='badge rounded-pill bg-info ${options.classes}'>{% trans "Building" %}: ${part.building}${part.units}</span>`;
|
||||
} else {
|
||||
// There is no stock
|
||||
return `<span class='badge rounded-pill bg-danger ${options.classes}'>{% trans "No Stock" %}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -1160,12 +1197,14 @@ function partGridTile(part) {
|
||||
|
||||
if (!part.in_stock) {
|
||||
stock = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock" %}</span>`;
|
||||
} else if (!part.unallocated_stock) {
|
||||
stock = `<span class='badge rounded-pill bg-warning'>{% trans "Not available" %}</span>`;
|
||||
}
|
||||
|
||||
rows += `<tr><td><b>{% trans "Stock" %}</b></td><td>${stock}</td></tr>`;
|
||||
|
||||
if (part.on_order) {
|
||||
rows += `<tr><td><b>{$ trans "On Order" %}</b></td><td>${part.on_order}</td></tr>`;
|
||||
if (part.ordering) {
|
||||
rows += `<tr><td><b>{% trans "On Order" %}</b></td><td>${part.ordering}</td></tr>`;
|
||||
}
|
||||
|
||||
if (part.building) {
|
||||
@ -1322,31 +1361,47 @@ function loadPartTable(table, url, options={}) {
|
||||
columns.push(col);
|
||||
|
||||
col = {
|
||||
field: 'in_stock',
|
||||
field: 'unallocated_stock',
|
||||
title: '{% trans "Stock" %}',
|
||||
searchable: false,
|
||||
formatter: function(value, row) {
|
||||
var link = '?display=part-stock';
|
||||
|
||||
if (value) {
|
||||
if (row.in_stock) {
|
||||
// There IS stock available for this part
|
||||
|
||||
// Is stock "low" (below the 'minimum_stock' quantity)?
|
||||
if (row.minimum_stock && row.minimum_stock > value) {
|
||||
if (row.minimum_stock && row.minimum_stock > row.in_stock) {
|
||||
value += `<span class='badge badge-right rounded-pill bg-warning'>{% trans "Low stock" %}</span>`;
|
||||
} else if (value == 0) {
|
||||
if (row.ordering) {
|
||||
// There is no available stock, but stock is on order
|
||||
value = `0<span class='badge badge-right rounded-pill bg-info'>{% trans "On Order" %}: ${row.ordering}</span>`;
|
||||
link = '?display=purchase-orders';
|
||||
} else if (row.building) {
|
||||
// There is no available stock, but stock is being built
|
||||
value = `0<span class='badge badge-right rounded-pill bg-info'>{% trans "Building" %}: ${row.building}</span>`;
|
||||
link = '?display=build-orders';
|
||||
} else {
|
||||
// There is no available stock
|
||||
value = `0<span class='badge badge-right rounded-pill bg-warning'>{% trans "No stock available" %}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (row.on_order) {
|
||||
// There is no stock available, but stock is on order
|
||||
value = `0<span class='badge badge-right rounded-pill bg-info'>{% trans "On Order" %}: ${row.on_order}</span>`;
|
||||
link = '?display=purchase-orders';
|
||||
} else if (row.building) {
|
||||
// There is no stock available, but stock is being built
|
||||
value = `0<span class='badge badge-right rounded-pill bg-info'>{% trans "Building" %}: ${row.building}</span>`;
|
||||
link = '?display=build-orders';
|
||||
} else {
|
||||
// There is no stock available
|
||||
value = `0<span class='badge badge-right rounded-pill bg-danger'>{% trans "No Stock" %}</span>`;
|
||||
// There IS NO stock available for this part
|
||||
|
||||
if (row.ordering) {
|
||||
// There is no stock, but stock is on order
|
||||
value = `0<span class='badge badge-right rounded-pill bg-info'>{% trans "On Order" %}: ${row.ordering}</span>`;
|
||||
link = '?display=purchase-orders';
|
||||
} else if (row.building) {
|
||||
// There is no stock, but stock is being built
|
||||
value = `0<span class='badge badge-right rounded-pill bg-info'>{% trans "Building" %}: ${row.building}</span>`;
|
||||
link = '?display=build-orders';
|
||||
} else {
|
||||
// There is no stock
|
||||
value = `0<span class='badge badge-right rounded-pill bg-danger'>{% trans "No Stock" %}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
return renderLink(value, `/part/${row.pk}/${link}`);
|
||||
|
330
InvenTree/templates/js/translated/search.js
Normal file
330
InvenTree/templates/js/translated/search.js
Normal file
@ -0,0 +1,330 @@
|
||||
{% load i18n %}
|
||||
|
||||
/* globals
|
||||
*/
|
||||
|
||||
/* exported
|
||||
closeSearchPanel,
|
||||
openSearchPanel,
|
||||
searchTextChanged,
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Callback when the search panel is closed
|
||||
*/
|
||||
function closeSearchPanel() {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Callback when the search panel is opened.
|
||||
* Ensure the panel is in a known state
|
||||
*/
|
||||
function openSearchPanel() {
|
||||
|
||||
var panel = $('#offcanvas-search');
|
||||
|
||||
clearSearchResults();
|
||||
|
||||
panel.find('#search-input').on('keyup change', searchTextChanged);
|
||||
|
||||
// Callback for "clear search" button
|
||||
panel.find('#search-clear').click(function(event) {
|
||||
|
||||
// Prevent this button from actually submitting the form
|
||||
event.preventDefault();
|
||||
|
||||
panel.find('#search-input').val('');
|
||||
clearSearchResults();
|
||||
});
|
||||
|
||||
// Callback for the "close search" button
|
||||
panel.find('#search-close').click(function(event) {
|
||||
// Prevent this button from actually submitting the form
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
var searchInputTimer = null;
|
||||
var searchText = null;
|
||||
var searchTextCurrent = null;
|
||||
var searchQueries = [];
|
||||
|
||||
function searchTextChanged(event) {
|
||||
|
||||
searchText = $('#offcanvas-search').find('#search-input').val();
|
||||
|
||||
clearTimeout(searchInputTimer);
|
||||
searchInputTimer = setTimeout(updateSearch, 250);
|
||||
};
|
||||
|
||||
|
||||
function updateSearch() {
|
||||
|
||||
if (searchText == searchTextCurrent) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearSearchResults();
|
||||
|
||||
if (searchText.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
searchTextCurrent = searchText;
|
||||
|
||||
// Cancel any previous AJAX requests
|
||||
searchQueries.forEach(function(query) {
|
||||
query.abort();
|
||||
});
|
||||
|
||||
searchQueries = [];
|
||||
|
||||
// Show the "searching" text
|
||||
$('#offcanvas-search').find('#search-pending').show();
|
||||
|
||||
if (user_settings.SEARCH_PREVIEW_SHOW_PARTS) {
|
||||
|
||||
var params = {};
|
||||
|
||||
if (user_settings.SEARCH_HIDE_INACTIVE_PARTS) {
|
||||
// Return *only* active parts
|
||||
params.active = true;
|
||||
}
|
||||
|
||||
// Search for matching parts
|
||||
addSearchQuery(
|
||||
'part',
|
||||
'{% trans "Parts" %}',
|
||||
'{% url "api-part-list" %}',
|
||||
params,
|
||||
renderPart,
|
||||
{
|
||||
url: '/part',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (user_settings.SEARCH_PREVIEW_SHOW_CATEGORIES) {
|
||||
// Search for matching part categories
|
||||
addSearchQuery(
|
||||
'category',
|
||||
'{% trans "Part Categories" %}',
|
||||
'{% url "api-part-category-list" %}',
|
||||
{},
|
||||
renderPartCategory,
|
||||
{
|
||||
url: '/part/category',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (user_settings.SEARCH_PREVIEW_SHOW_STOCK) {
|
||||
// Search for matching stock items
|
||||
addSearchQuery(
|
||||
'stock',
|
||||
'{% trans "Stock Items" %}',
|
||||
'{% url "api-stock-list" %}',
|
||||
{
|
||||
part_detail: true,
|
||||
location_detail: true,
|
||||
},
|
||||
renderStockItem,
|
||||
{
|
||||
url: '/stock/item',
|
||||
render_location_detail: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (user_settings.SEARCH_PREVIEW_SHOW_LOCATIONS) {
|
||||
// Search for matching stock locations
|
||||
addSearchQuery(
|
||||
'location',
|
||||
'{% trans "Stock Locations" %}',
|
||||
'{% url "api-location-list" %}',
|
||||
{},
|
||||
renderStockLocation,
|
||||
{
|
||||
url: '/stock/location',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (user_settings.SEARCH_PREVIEW_SHOW_COMPANIES) {
|
||||
// Search for matching companies
|
||||
addSearchQuery(
|
||||
'company',
|
||||
'{% trans "Companies" %}',
|
||||
'{% url "api-company-list" %}',
|
||||
{},
|
||||
renderCompany,
|
||||
{
|
||||
url: '/company',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (user_settings.SEARCH_PREVIEW_SHOW_PURCHASE_ORDERS) {
|
||||
// Search for matching purchase orders
|
||||
addSearchQuery(
|
||||
'purchaseorder',
|
||||
'{% trans "Purchase Orders" %}',
|
||||
'{% url "api-po-list" %}',
|
||||
{
|
||||
supplier_detail: true,
|
||||
outstanding: true,
|
||||
},
|
||||
renderPurchaseOrder,
|
||||
{
|
||||
url: '/order/purchase-order',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (user_settings.SEARCH_PREVIEW_SHOW_SALES_ORDERS) {
|
||||
// Search for matching sales orders
|
||||
addSearchQuery(
|
||||
'salesorder',
|
||||
'{% trans "Sales Orders" %}',
|
||||
'{% url "api-so-list" %}',
|
||||
{
|
||||
customer_detail: true,
|
||||
outstanding: true,
|
||||
},
|
||||
renderSalesOrder,
|
||||
{
|
||||
url: '/order/sales-order',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Wait until all the pending queries are completed
|
||||
$.when.apply($, searchQueries).done(function() {
|
||||
$('#offcanvas-search').find('#search-pending').hide();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function clearSearchResults() {
|
||||
|
||||
var panel = $('#offcanvas-search');
|
||||
|
||||
// Ensure the 'no results found' element is visible
|
||||
panel.find('#search-no-results').show();
|
||||
|
||||
// Ensure that the 'searching' element is hidden
|
||||
panel.find('#search-pending').hide();
|
||||
|
||||
// Delete any existing search results
|
||||
panel.find('#search-results').empty();
|
||||
|
||||
// Finally, grab keyboard focus in the search bar
|
||||
panel.find('#search-input').focus();
|
||||
}
|
||||
|
||||
|
||||
function addSearchQuery(key, title, query_url, query_params, render_func, render_params={}) {
|
||||
|
||||
// Include current search term
|
||||
query_params.search = searchTextCurrent;
|
||||
|
||||
// How many results to show in each group?
|
||||
query_params.offset = 0;
|
||||
query_params.limit = user_settings.SEARCH_PREVIEW_RESULTS;
|
||||
|
||||
// Do not display "pk" value for search results
|
||||
render_params.render_pk = false;
|
||||
|
||||
// Add the result group to the panel
|
||||
$('#offcanvas-search').find('#search-results').append(`
|
||||
<div class='search-result-group-wrapper' id='search-results-wrapper-${key}'></div>
|
||||
`);
|
||||
|
||||
var request = inventreeGet(
|
||||
query_url,
|
||||
query_params,
|
||||
{
|
||||
success: function(response) {
|
||||
addSearchResults(
|
||||
key,
|
||||
response.results,
|
||||
title,
|
||||
render_func,
|
||||
render_params,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Add the query to the stack
|
||||
searchQueries.push(request);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Add a group of results to the list
|
||||
function addSearchResults(key, results, title, renderFunc, renderParams={}) {
|
||||
|
||||
if (results.length == 0) {
|
||||
// Do not display this group, as there are no results
|
||||
return;
|
||||
}
|
||||
|
||||
var panel = $('#offcanvas-search');
|
||||
|
||||
// Ensure the 'no results found' element is hidden
|
||||
panel.find('#search-no-results').hide();
|
||||
|
||||
panel.find(`#search-results-wrapper-${key}`).append(`
|
||||
<div class='search-result-group' id='search-results-${key}'>
|
||||
<div class='search-result-header' style='display: flex;'>
|
||||
<h5>${title}</h5>
|
||||
<span class='flex' style='flex-grow: 1;'></span>
|
||||
<div class='search-result-group-buttons btn-group float-right' role='group'>
|
||||
<button class='btn btn-outline-secondary' id='hide-results-${key}' title='{% trans "Minimize results" %}'>
|
||||
<span class='fas fa-chevron-up'></span>
|
||||
</button>
|
||||
<button class='btn btn-outline-secondary' id='remove-results-${key}' title='{% trans "Remove results" %}'>
|
||||
<span class='fas fa-times icon-red'></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class='collapse search-result-list' id='search-result-list-${key}'>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
results.forEach(function(result) {
|
||||
|
||||
var pk = result.pk || result.id;
|
||||
|
||||
var html = renderFunc(key, result, renderParams);
|
||||
|
||||
if (renderParams.url) {
|
||||
html = `<a href='${renderParams.url}/${pk}/'>` + html + `</a>`;
|
||||
}
|
||||
|
||||
var result_html = `
|
||||
<div class='search-result-entry' id='search-result-${key}-${pk}'>
|
||||
${html}
|
||||
</div>
|
||||
`;
|
||||
|
||||
panel.find(`#search-result-list-${key}`).append(result_html);
|
||||
});
|
||||
|
||||
// Expand results panel
|
||||
panel.find(`#search-result-list-${key}`).toggle();
|
||||
|
||||
// Add callback for "toggle" button
|
||||
panel.find(`#hide-results-${key}`).click(function() {
|
||||
panel.find(`#search-result-list-${key}`).toggle();
|
||||
});
|
||||
|
||||
// Add callback for "remove" button
|
||||
panel.find(`#remove-results-${key}`).click(function() {
|
||||
panel.find(`#search-results-${key}`).remove();
|
||||
});
|
||||
}
|
@ -1770,6 +1770,7 @@ function loadStockTable(table, options) {
|
||||
col = {
|
||||
field: 'location_detail.pathstring',
|
||||
title: '{% trans "Location" %}',
|
||||
sortName: 'location',
|
||||
formatter: function(value, row) {
|
||||
return locationDetail(row);
|
||||
}
|
||||
@ -1912,172 +1913,8 @@ function loadStockTable(table, options) {
|
||||
original: original,
|
||||
showColumns: true,
|
||||
columns: columns,
|
||||
{% if False %}
|
||||
groupByField: options.groupByField || 'part',
|
||||
groupBy: grouping,
|
||||
groupByFormatter: function(field, id, data) {
|
||||
|
||||
var row = data[0];
|
||||
|
||||
if (field == 'part_detail.full_name') {
|
||||
|
||||
var html = imageHoverIcon(row.part_detail.thumbnail);
|
||||
|
||||
html += row.part_detail.full_name;
|
||||
html += ` <i>(${data.length} {% trans "items" %})</i>`;
|
||||
|
||||
html += makePartIcons(row.part_detail);
|
||||
|
||||
return html;
|
||||
} else if (field == 'part_detail.IPN') {
|
||||
var ipn = row.part_detail.IPN;
|
||||
|
||||
if (ipn) {
|
||||
return ipn;
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
} else if (field == 'part_detail.description') {
|
||||
return row.part_detail.description;
|
||||
} else if (field == 'packaging') {
|
||||
var packaging = [];
|
||||
|
||||
data.forEach(function(item) {
|
||||
var pkg = item.packaging;
|
||||
|
||||
if (!pkg) {
|
||||
pkg = '-';
|
||||
}
|
||||
|
||||
if (!packaging.includes(pkg)) {
|
||||
packaging.push(pkg);
|
||||
}
|
||||
});
|
||||
|
||||
if (packaging.length > 1) {
|
||||
return "...";
|
||||
} else if (packaging.length == 1) {
|
||||
return packaging[0];
|
||||
} else {
|
||||
return "-";
|
||||
}
|
||||
} else if (field == 'quantity') {
|
||||
var stock = 0;
|
||||
var items = 0;
|
||||
|
||||
data.forEach(function(item) {
|
||||
stock += parseFloat(item.quantity);
|
||||
items += 1;
|
||||
});
|
||||
|
||||
stock = +stock.toFixed(5);
|
||||
|
||||
return `${stock} (${items} {% trans "items" %})`;
|
||||
} else if (field == 'status') {
|
||||
var statii = [];
|
||||
|
||||
data.forEach(function(item) {
|
||||
var status = String(item.status);
|
||||
|
||||
if (!status || status == '') {
|
||||
status = '-';
|
||||
}
|
||||
|
||||
if (!statii.includes(status)) {
|
||||
statii.push(status);
|
||||
}
|
||||
});
|
||||
|
||||
// Multiple status codes
|
||||
if (statii.length > 1) {
|
||||
return "...";
|
||||
} else if (statii.length == 1) {
|
||||
return stockStatusDisplay(statii[0]);
|
||||
} else {
|
||||
return "-";
|
||||
}
|
||||
} else if (field == 'batch') {
|
||||
var batches = [];
|
||||
|
||||
data.forEach(function(item) {
|
||||
var batch = item.batch;
|
||||
|
||||
if (!batch || batch == '') {
|
||||
batch = '-';
|
||||
}
|
||||
|
||||
if (!batches.includes(batch)) {
|
||||
batches.push(batch);
|
||||
}
|
||||
});
|
||||
|
||||
if (batches.length > 1) {
|
||||
return "" + batches.length + " {% trans 'batches' %}";
|
||||
} else if (batches.length == 1) {
|
||||
if (batches[0]) {
|
||||
return batches[0];
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
} else if (field == 'location_detail.pathstring') {
|
||||
/* Determine how many locations */
|
||||
var locations = [];
|
||||
|
||||
data.forEach(function(item) {
|
||||
|
||||
var detail = locationDetail(item);
|
||||
|
||||
if (!locations.includes(detail)) {
|
||||
locations.push(detail);
|
||||
}
|
||||
});
|
||||
|
||||
if (locations.length == 1) {
|
||||
// Single location, easy!
|
||||
return locations[0];
|
||||
} else if (locations.length > 1) {
|
||||
return "In " + locations.length + " {% trans 'locations' %}";
|
||||
} else {
|
||||
return "<i>{% trans 'Undefined location' %}</i>";
|
||||
}
|
||||
} else if (field == 'notes') {
|
||||
var notes = [];
|
||||
|
||||
data.forEach(function(item) {
|
||||
var note = item.notes;
|
||||
|
||||
if (!note || note == '') {
|
||||
note = '-';
|
||||
}
|
||||
|
||||
if (!notes.includes(note)) {
|
||||
notes.push(note);
|
||||
}
|
||||
});
|
||||
|
||||
if (notes.length > 1) {
|
||||
return '...';
|
||||
} else if (notes.length == 1) {
|
||||
return notes[0] || '-';
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
{% endif %}
|
||||
});
|
||||
|
||||
/*
|
||||
if (options.buttons) {
|
||||
linkButtonsToSelection(table, options.buttons);
|
||||
}
|
||||
*/
|
||||
|
||||
var buttons = [
|
||||
'#stock-print-options',
|
||||
'#stock-options',
|
||||
@ -2092,7 +1929,6 @@ function loadStockTable(table, options) {
|
||||
buttons,
|
||||
);
|
||||
|
||||
|
||||
function stockAdjustment(action) {
|
||||
var items = $(table).bootstrapTable('getSelections');
|
||||
|
||||
@ -2136,7 +1972,7 @@ function loadStockTable(table, options) {
|
||||
var items = [];
|
||||
|
||||
selections.forEach(function(item) {
|
||||
items.push(item.pk);
|
||||
items.push(item);
|
||||
});
|
||||
|
||||
scanItemsIntoLocation(items);
|
||||
|
@ -427,12 +427,16 @@ function getAvailableTableFilters(tableKey) {
|
||||
},
|
||||
has_stock: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Stock available" %}',
|
||||
title: '{% trans "In stock" %}',
|
||||
},
|
||||
low_stock: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Low stock" %}',
|
||||
},
|
||||
unallocated_stock: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Available stock" %}',
|
||||
},
|
||||
assembly: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Assembly" %}',
|
||||
|
Reference in New Issue
Block a user