2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-01 19:20:55 +00:00

Merge branch 'inventree:master' into matmair/issue2694

This commit is contained in:
Matthias Mair
2022-04-05 02:03:40 +02:00
committed by GitHub
54 changed files with 20257 additions and 15514 deletions

View File

@ -1746,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) {

View File

@ -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'>

View File

@ -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;

View File

@ -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}`);

View File

@ -0,0 +1,329 @@
{% 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) {
params.active = false;
}
// 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();
});
}

View File

@ -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" %}',