').append(html).appendTo(ul);
- };
- },
- select: function( event, ui ) {
- window.location = '/part/' + ui.item.id + '/';
- },
- minLength: 2,
- classes: {
- 'ui-autocomplete': 'dropdown-menu search-menu',
- },
- position: {
- my : "right top",
- at: "right bottom"
- }
- });
- }
-
// Generate brand-icons
$('.brand-icon').each(function(i, obj) {
loadBrandIcon($(this), $(this).attr('brand_name'));
@@ -231,8 +156,13 @@ function inventreeDocReady() {
stopNotificationWatcher();
});
- $('#offcanvasRight').on('show.bs.offcanvas', openNotificationPanel); // listener for opening the notification panel
- $('#offcanvasRight').on('hidden.bs.offcanvas', closeNotificationPanel); // listener for closing the notification panel
+ // Calbacks for search panel
+ $('#offcanvas-search').on('shown.bs.offcanvas', openSearchPanel);
+ $('#offcanvas-search').on('hidden.bs.offcanvas', closeSearchPanel);
+
+ // Callbacks for notifications panel
+ $('#offcanvas-notification').on('show.bs.offcanvas', openNotificationPanel); // listener for opening the notification panel
+ $('#offcanvas-notification').on('hidden.bs.offcanvas', closeNotificationPanel); // listener for closing the notification panel
}
diff --git a/InvenTree/InvenTree/test_views.py b/InvenTree/InvenTree/test_views.py
index 56d8889984..490e91b03e 100644
--- a/InvenTree/InvenTree/test_views.py
+++ b/InvenTree/InvenTree/test_views.py
@@ -72,7 +72,7 @@ class ViewTests(TestCase):
"""
# Change this number as more javascript files are added to the index page
- N_SCRIPT_FILES = 37
+ N_SCRIPT_FILES = 38
content = self.get_index_page()
diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py
index d795b81472..ec8b891f93 100644
--- a/InvenTree/InvenTree/urls.py
+++ b/InvenTree/InvenTree/urls.py
@@ -130,6 +130,7 @@ translated_javascript_urls = [
url(r'^order.js', DynamicJsView.as_view(template_name='js/translated/order.js'), name='order.js'),
url(r'^part.js', DynamicJsView.as_view(template_name='js/translated/part.js'), name='part.js'),
url(r'^report.js', DynamicJsView.as_view(template_name='js/translated/report.js'), name='report.js'),
+ url(r'^search.js', DynamicJsView.as_view(template_name='js/translated/search.js'), name='search.js'),
url(r'^stock.js', DynamicJsView.as_view(template_name='js/translated/stock.js'), name='stock.js'),
url(r'^plugin.js', DynamicJsView.as_view(template_name='js/translated/plugin.js'), name='plugin.js'),
url(r'^tables.js', DynamicJsView.as_view(template_name='js/translated/tables.js'), name='tables.js'),
diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py
index 6d4848f436..0a9d39225b 100644
--- a/InvenTree/InvenTree/version.py
+++ b/InvenTree/InvenTree/version.py
@@ -12,11 +12,24 @@ import common.models
INVENTREE_SW_VERSION = "0.7.0 dev"
# InvenTree API version
-INVENTREE_API_VERSION = 32
+INVENTREE_API_VERSION = 36
"""
Increment this API version number whenever there is a significant change to the API that any clients need to know about
+v36 -> 2022-04-03
+ - Adds ability to filter part list endpoint by unallocated_stock argument
+
+v35 -> 2022-04-01 : https://github.com/inventree/InvenTree/pull/2797
+ - Adds stock allocation information to the Part API
+ - Adds calculated field for "unallocated_quantity"
+
+v34 -> 2022-03-25
+ - Change permissions for "plugin list" API endpoint (now allows any authenticated user)
+
+v33 -> 2022-03-24
+ - Adds "plugins_enabled" information to root API endpoint
+
v32 -> 2022-03-19
- Adds "parameters" detail to Part API endpoint (use ¶meters=true)
- Adds ability to filter PartParameterTemplate API by Part instance
@@ -190,7 +203,7 @@ def isInvenTreeUpToDate():
and stores it to the database as INVENTREE_LATEST_VERSION
"""
- latest = common.models.InvenTreeSetting.get_setting('INVENTREE_LATEST_VERSION', None)
+ latest = common.models.InvenTreeSetting.get_setting('INVENTREE_LATEST_VERSION', backup_value=None, create=False)
# No record for "latest" version - we must assume we are up to date!
if not latest:
diff --git a/InvenTree/build/templates/build/delete_build.html b/InvenTree/build/templates/build/delete_build.html
index 7c3298ba35..65e53b2f80 100644
--- a/InvenTree/build/templates/build/delete_build.html
+++ b/InvenTree/build/templates/build/delete_build.html
@@ -1,5 +1,7 @@
{% extends "modal_delete_form.html" %}
-
+{% load i18n %}
{% block pre_form_content %}
-Are you sure you want to delete this build?
+
+{% trans "Are you sure you want to delete this build?" %}
+
{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html
index 21bddfd6ca..0ed3c01f7e 100644
--- a/InvenTree/build/templates/build/detail.html
+++ b/InvenTree/build/templates/build/detail.html
@@ -258,6 +258,19 @@
diff --git a/InvenTree/templates/InvenTree/notifications/notifications.html b/InvenTree/templates/InvenTree/notifications/notifications.html
index 89f10c3e3d..fedf8a1448 100644
--- a/InvenTree/templates/InvenTree/notifications/notifications.html
+++ b/InvenTree/templates/InvenTree/notifications/notifications.html
@@ -126,8 +126,12 @@ $("#mark-all").on('click', function() {
{
read: false,
},
+ {
+ success: function(response) {
+ updateNotificationTables();
+ }
+ }
);
- updateNotificationTables();
});
loadNotificationTable("#history-table", {
diff --git a/InvenTree/templates/InvenTree/settings/settings.html b/InvenTree/templates/InvenTree/settings/settings.html
index ba27187747..dbb526cfd3 100644
--- a/InvenTree/templates/InvenTree/settings/settings.html
+++ b/InvenTree/templates/InvenTree/settings/settings.html
@@ -22,6 +22,7 @@
{% include "InvenTree/settings/user_settings.html" %}
{% include "InvenTree/settings/user_homepage.html" %}
{% include "InvenTree/settings/user_search.html" %}
+{% include "InvenTree/settings/user_notifications.html" %}
{% include "InvenTree/settings/user_labels.html" %}
{% include "InvenTree/settings/user_reports.html" %}
{% include "InvenTree/settings/user_display.html" %}
diff --git a/InvenTree/templates/InvenTree/settings/sidebar.html b/InvenTree/templates/InvenTree/settings/sidebar.html
index 85e0b4ce94..9df6b0736f 100644
--- a/InvenTree/templates/InvenTree/settings/sidebar.html
+++ b/InvenTree/templates/InvenTree/settings/sidebar.html
@@ -14,6 +14,8 @@
{% include "sidebar_item.html" with label='user-home' text=text icon="fa-home" %}
{% trans "Search Settings" as text %}
{% include "sidebar_item.html" with label='user-search' text=text icon="fa-search" %}
+{% trans "Notifications" as text %}
+{% include "sidebar_item.html" with label='user-notifications' text=text icon="fa-bell" %}
{% trans "Label Printing" as text %}
{% include "sidebar_item.html" with label='user-labels' text=text icon="fa-tag" %}
{% trans "Reporting" as text %}
diff --git a/InvenTree/templates/InvenTree/settings/user_labels.html b/InvenTree/templates/InvenTree/settings/user_labels.html
index a2d9b7b89c..6a27ef05bc 100644
--- a/InvenTree/templates/InvenTree/settings/user_labels.html
+++ b/InvenTree/templates/InvenTree/settings/user_labels.html
@@ -14,6 +14,7 @@
+ {% include "InvenTree/settings/setting.html" with key="LABEL_ENABLE" icon='fa-toggle-on' user_setting=True %}
{% include "InvenTree/settings/setting.html" with key="LABEL_INLINE" icon='fa-tag' user_setting=True %}
+
+ {% include "InvenTree/settings/setting.html" with key="NOTIFICATION_SEND_EMAILS" icon='fa-envelope' user_setting=True %}
+
+
+
+
+{% endblock content %}
\ No newline at end of file
diff --git a/InvenTree/templates/InvenTree/settings/user_search.html b/InvenTree/templates/InvenTree/settings/user_search.html
index 51df53ee6b..1883110b80 100644
--- a/InvenTree/templates/InvenTree/settings/user_search.html
+++ b/InvenTree/templates/InvenTree/settings/user_search.html
@@ -14,8 +14,16 @@
+ {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_PARTS" user_setting=True icon='fa-shapes' %}
+ {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_CATEGORIES" user_setting=True icon='fa-sitemap' %}
+ {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_STOCK" user_setting=True icon='fa-boxes' %}
+ {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_LOCATIONS" user_setting=True icon='fa-sitemap' %}
+ {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_COMPANIES" user_setting=True icon='fa-building' %}
+ {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_PURCHASE_ORDERS" user_setting=True icon='fa-shopping-cart' %}
+ {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_SALES_ORDERS" user_setting=True icon='fa-truck' %}
+
{% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_RESULTS" user_setting=True icon='fa-search' %}
- {% include "InvenTree/settings/setting.html" with key="SEARCH_SHOW_STOCK_LEVELS" user_setting=True icon='fa-boxes' %}
+
{% include "InvenTree/settings/setting.html" with key="SEARCH_HIDE_INACTIVE_PARTS" user_setting=True icon='fa-eye-slash' %}
diff --git a/InvenTree/templates/base.html b/InvenTree/templates/base.html
index f916344bf9..d3c8ed8a59 100644
--- a/InvenTree/templates/base.html
+++ b/InvenTree/templates/base.html
@@ -6,6 +6,7 @@
{% settings_value 'REPORT_ENABLE_TEST_REPORT' as test_report_enabled %}
{% settings_value "REPORT_ENABLE" as report_enabled %}
{% settings_value "SERVER_RESTART_REQUIRED" as server_restart_required %}
+{% settings_value "LABEL_ENABLE" with user=user as labels_enabled %}
{% inventree_demo_mode as demo_mode %}
@@ -126,9 +127,11 @@
{% endblock %}
+
{% include 'modals.html' %}
{% include 'about.html' %}
{% include "notifications.html" %}
+ {% include "search.html" %}
@@ -185,6 +188,7 @@
+
diff --git a/InvenTree/templates/js/dynamic/settings.js b/InvenTree/templates/js/dynamic/settings.js
index 4e7d36f72b..2832bd3482 100644
--- a/InvenTree/templates/js/dynamic/settings.js
+++ b/InvenTree/templates/js/dynamic/settings.js
@@ -4,6 +4,7 @@
editSetting,
user_settings,
global_settings,
+ plugins_enabled,
*/
{% user_settings request.user as USER_SETTINGS %}
@@ -20,6 +21,13 @@ const global_settings = {
{% endfor %}
};
+{% plugins_enabled as p_en %}
+{% if p_en %}
+const plugins_enabled = true;
+{% else %}
+const plugins_enabled = false;
+{% endif %}
+
/*
* Edit a setting value
*/
diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js
index 5b416b4d22..d4db965ebd 100644
--- a/InvenTree/templates/js/translated/build.js
+++ b/InvenTree/templates/js/translated/build.js
@@ -213,7 +213,7 @@ function createBuildOutput(build_id, options) {
success: function(data) {
if (data.next) {
fields.serial_numbers.placeholder = `{% trans "Next available serial number" %}: ${data.next}`;
- } else {
+ } else if (data.latest) {
fields.serial_numbers.placeholder = `{% trans "Latest serial number" %}: ${data.latest}`;
}
},
@@ -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) {
diff --git a/InvenTree/templates/js/translated/label.js b/InvenTree/templates/js/translated/label.js
index 1c843917e6..d19c403861 100644
--- a/InvenTree/templates/js/translated/label.js
+++ b/InvenTree/templates/js/translated/label.js
@@ -10,15 +10,46 @@
modalSetTitle,
modalSubmit,
openModal,
+ plugins_enabled,
showAlertDialog,
*/
/* exported
+ printLabels,
printPartLabels,
printStockItemLabels,
printStockLocationLabels,
*/
+
+/*
+ * Perform the "print" action.
+ */
+function printLabels(url, plugin=null) {
+
+ if (plugin) {
+ // If a plugin is provided, do not redirect the browser.
+ // Instead, perform an API request and display a message
+
+ url = url + `plugin=${plugin}`;
+
+ inventreeGet(url, {}, {
+ success: function(response) {
+ showMessage(
+ '{% trans "Labels sent to printer" %}',
+ {
+ style: 'success',
+ }
+ );
+ }
+ });
+ } else {
+ window.location.href = url;
+ }
+
+}
+
+
function printStockItemLabels(items) {
/**
* Print stock item labels for the given stock items
@@ -57,14 +88,17 @@ function printStockItemLabels(items) {
response,
items,
{
- success: function(pk) {
+ success: function(data) {
+
+ var pk = data.label;
+
var href = `/api/label/stock/${pk}/print/?`;
items.forEach(function(item) {
href += `items[]=${item}&`;
});
- window.location.href = href;
+ printLabels(href, data.plugin);
}
}
);
@@ -73,6 +107,7 @@ function printStockItemLabels(items) {
);
}
+
function printStockLocationLabels(locations) {
if (locations.length == 0) {
@@ -107,14 +142,17 @@ function printStockLocationLabels(locations) {
response,
locations,
{
- success: function(pk) {
+ success: function(data) {
+
+ var pk = data.label;
+
var href = `/api/label/location/${pk}/print/?`;
locations.forEach(function(location) {
href += `locations[]=${location}&`;
});
- window.location.href = href;
+ printLabels(href, data.plugin);
}
}
);
@@ -162,14 +200,17 @@ function printPartLabels(parts) {
response,
parts,
{
- success: function(pk) {
- var url = `/api/label/part/${pk}/print/?`;
+ success: function(data) {
+
+ var pk = data.label;
+
+ var href = `/api/label/part/${pk}/print/?`;
parts.forEach(function(part) {
- url += `parts[]=${part}&`;
+ href += `parts[]=${part}&`;
});
- window.location.href = url;
+ printLabels(href, data.plugin);
}
}
);
@@ -188,17 +229,52 @@ function selectLabel(labels, items, options={}) {
* (via AJAX) from the server.
*/
- // If only a single label template is provided,
- // just run with that!
+ // Array of available plugins for label printing
+ var plugins = [];
- if (labels.length == 1) {
- if (options.success) {
- options.success(labels[0].pk);
- }
-
- return;
+ // Request a list of available label printing plugins from the server
+ 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_enabled && plugins.length > 0) {
+ plugin_selection =`
+
+
+
+
+
+
+ `;
+ }
var modal = options.modal || '#modal-form';
@@ -233,14 +309,15 @@ function selectLabel(labels, items, options={}) {
`;
openModal({
@@ -255,14 +332,17 @@ function selectLabel(labels, items, options={}) {
modalSubmit(modal, function() {
- var label = $(modal).find('#id_label');
-
- var pk = label.val();
+ var label = $(modal).find('#id_label').val();
+ var plugin = $(modal).find('#id_plugin').val();
closeModal(modal);
if (options.success) {
- options.success(pk);
+ options.success({
+ // Return the selected label template and plugin
+ label: label,
+ plugin: plugin,
+ });
}
});
}
diff --git a/InvenTree/templates/js/translated/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js
index e3abe1186f..8c98fa35de 100644
--- a/InvenTree/templates/js/translated/model_renderers.js
+++ b/InvenTree/templates/js/translated/model_renderers.js
@@ -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 `${title}: ${pk}`;
+ } 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 += `${data.name} - ${data.description}`;
- html += `{% trans "Company ID" %}: ${data.pk}`;
+ 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 = `${data.part_detail.full_name} - `;
}
- 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 = `{% trans "Stock ID" %}: ${data.pk}`;
- }
-
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 = ` - (${data.location_detail.name})`;
+ location_detail = ` - (${data.location_detail.name})`;
}
var stock_detail = '';
@@ -101,7 +109,10 @@ function renderStockItem(name, data, parameters, options) {
var html = `
- ${part_detail}${stock_detail}${location_detail}${stock_id}
+ ${part_detail}
+ ${stock_detail}
+ ${location_detail}
+ ${renderId('{% trans "Stock ID" %}', data.pk, parameters)}
`;
@@ -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 += ` - ${data.description}`;
}
- 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 += `{% trans "Inactive" %}`;
}
@@ -178,8 +190,9 @@ function renderPart(name, data, parameters, options) {
html += `
+ ${stock_data}
${extra}
- {% trans "Part ID" %}: ${data.pk}
+ ${renderId('{% trans "Part ID" $}', data.pk, parameters)}
`;
@@ -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 = `${data.username}`;
@@ -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 = `${data.name}`;
@@ -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 = `${prefix}${data.reference}`;
var thumbnail = null;
- html += `${prefix}${data.reference}`;
-
if (data.supplier_detail) {
thumbnail = data.supplier_detail.thumbnail || data.supplier_detail.image;
@@ -243,13 +254,7 @@ function renderPurchaseOrder(name, data, parameters, options) {
html += ` - ${data.description}`;
}
- html += `
-
-
- {% trans "Order ID" %}: ${data.pk}
-
-
- `;
+ 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 = `${data.reference}`;
+function renderSalesOrder(name, data, parameters={}, options={}) {
+
+ var prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
+ var html = `${prefix}${data.reference}`;
+
+ var thumbnail = null;
+
+ if (data.customer_detail) {
+ thumbnail = data.customer_detail.thumbnail || data.customer_detail.image;
+
+ html += ' - ' + select2Thumbnail(thumbnail);
+ html += `${data.customer_detail.name}`;
+ }
if (data.description) {
html += ` - ${data.description}`;
}
- html += `
-
-
- {% trans "Order ID" %}: ${data.pk}
-
- `;
+ 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;
diff --git a/InvenTree/templates/js/translated/notification.js b/InvenTree/templates/js/translated/notification.js
index 4da4c2e875..a289dcb8f7 100644
--- a/InvenTree/templates/js/translated/notification.js
+++ b/InvenTree/templates/js/translated/notification.js
@@ -253,7 +253,7 @@ function openNotificationPanel() {
{
success: function(response) {
if (response.length == 0) {
- html = `
{% trans "No unread notifications" %}
`;
+ html = `
{% trans "No unread notifications" %}
`;
} else {
// build up items
response.forEach(function(item, index) {
diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js
index b0283a1b35..08b258fdc2 100644
--- a/InvenTree/templates/js/translated/part.js
+++ b/InvenTree/templates/js/translated/part.js
@@ -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 `{% trans "Stock" %}: ${part.in_stock}`;
+ // 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 `{% trans "Low stock" %}: ${part.in_stock}${part.units}`;
+ } else if (part.unallocated_stock == 0) {
+ if (part.ordering) {
+ // There is no available stock, but stock is on order
+ return `{% trans "On Order" %}: ${part.ordering}${part.units}`;
+ } else if (part.building) {
+ // There is no available stock, but stock is being built
+ return `{% trans "Building" %}: ${part.building}${part.units}`;
+ } else {
+ // There is no available stock at all
+ return `{% trans "No stock available" %}`;
+ }
+ } else if (part.unallocated_stock < part.in_stock) {
+ // Unallocated quanttiy is less than total quantity
+ return `{% trans "Available" %}: ${part.unallocated_stock}/${part.in_stock}${part.units}`;
+ } else {
+ // Stock is completely available
+ return `{% trans "Available" %}: ${part.unallocated_stock}${part.units}`;
+ }
} else {
- return `{% trans "No Stock" %}`;
+ // There IS NO stock available for this part
+
+ if (part.ordering) {
+ // There is no stock, but stock is on order
+ return `{% trans "On Order" %}: ${part.ordering}${part.units}`;
+ } else if (part.building) {
+ // There is no stock, but stock is being built
+ return `{% trans "Building" %}: ${part.building}${part.units}`;
+ } else {
+ // There is no stock
+ return `{% trans "No Stock" %}`;
+ }
}
+
}
@@ -1160,12 +1197,14 @@ function partGridTile(part) {
if (!part.in_stock) {
stock = `{% trans "No Stock" %}`;
+ } else if (!part.unallocated_stock) {
+ stock = `{% trans "Not available" %}`;
}
rows += `
{% trans "Stock" %}
${stock}
`;
- if (part.on_order) {
- rows += `
{$ trans "On Order" %}
${part.on_order}
`;
+ if (part.ordering) {
+ rows += `
{% trans "On Order" %}
${part.ordering}
`;
}
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 += `{% trans "Low stock" %}`;
+ } else if (value == 0) {
+ if (row.ordering) {
+ // There is no available stock, but stock is on order
+ value = `0{% trans "On Order" %}: ${row.ordering}`;
+ link = '?display=purchase-orders';
+ } else if (row.building) {
+ // There is no available stock, but stock is being built
+ value = `0{% trans "Building" %}: ${row.building}`;
+ link = '?display=build-orders';
+ } else {
+ // There is no available stock
+ value = `0{% trans "No stock available" %}`;
+ }
}
-
- } else if (row.on_order) {
- // There is no stock available, but stock is on order
- value = `0{% trans "On Order" %}: ${row.on_order}`;
- link = '?display=purchase-orders';
- } else if (row.building) {
- // There is no stock available, but stock is being built
- value = `0{% trans "Building" %}: ${row.building}`;
- link = '?display=build-orders';
} else {
- // There is no stock available
- value = `0{% trans "No Stock" %}`;
+ // There IS NO stock available for this part
+
+ if (row.ordering) {
+ // There is no stock, but stock is on order
+ value = `0{% trans "On Order" %}: ${row.ordering}`;
+ link = '?display=purchase-orders';
+ } else if (row.building) {
+ // There is no stock, but stock is being built
+ value = `0{% trans "Building" %}: ${row.building}`;
+ link = '?display=build-orders';
+ } else {
+ // There is no stock
+ value = `0{% trans "No Stock" %}`;
+ }
}
return renderLink(value, `/part/${row.pk}/${link}`);
diff --git a/InvenTree/templates/js/translated/search.js b/InvenTree/templates/js/translated/search.js
new file mode 100644
index 0000000000..c0c1a07db7
--- /dev/null
+++ b/InvenTree/templates/js/translated/search.js
@@ -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(`
+
+ `);
+
+ 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(`
+
+
+
${title}
+
+
+
+
+
+
+
+
+
+ `);
+
+ results.forEach(function(result) {
+
+ var pk = result.pk || result.id;
+
+ var html = renderFunc(key, result, renderParams);
+
+ if (renderParams.url) {
+ html = `` + html + ``;
+ }
+
+ var result_html = `
+
+ ${html}
+
+ `;
+
+ 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();
+ });
+}
diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js
index b7f4162621..ade8bc5a0a 100644
--- a/InvenTree/templates/js/translated/stock.js
+++ b/InvenTree/templates/js/translated/stock.js
@@ -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 += ` (${data.length} {% trans "items" %})`;
-
- 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 "{% trans 'Undefined location' %}";
- }
- } 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');
diff --git a/InvenTree/templates/js/translated/table_filters.js b/InvenTree/templates/js/translated/table_filters.js
index 81d43d2c3f..6212568950 100644
--- a/InvenTree/templates/js/translated/table_filters.js
+++ b/InvenTree/templates/js/translated/table_filters.js
@@ -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" %}',
diff --git a/InvenTree/templates/navbar.html b/InvenTree/templates/navbar.html
index 126376a7dc..d687300eb4 100644
--- a/InvenTree/templates/navbar.html
+++ b/InvenTree/templates/navbar.html
@@ -87,18 +87,25 @@
{% if demo %}
{% include "navbar_demo.html" %}
{% endif %}
- {% include "search_form.html" %}
+
+
+
+
+
+
{% if barcodes %}
-
{% endif %}
-
+ {% trans "New Notifications" %}
diff --git a/InvenTree/templates/notifications.html b/InvenTree/templates/notifications.html
index bec96d1585..e7dfc86998 100644
--- a/InvenTree/templates/notifications.html
+++ b/InvenTree/templates/notifications.html
@@ -1,7 +1,8 @@
{% load i18n %}
-