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_notifications.html b/InvenTree/templates/InvenTree/settings/user_notifications.html
new file mode 100644
index 0000000000..4e9889ca69
--- /dev/null
+++ b/InvenTree/templates/InvenTree/settings/user_notifications.html
@@ -0,0 +1,20 @@
+{% extends "panel.html" %}
+
+{% load i18n %}
+{% load inventree_extras %}
+
+{% block label %}user-notifications{% endblock label %}
+
+{% block heading %}{% trans "Notification Settings" %}{% endblock heading %}
+
+{% block content %}
+
+
+
+
+ {% 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/js/translated/build.js b/InvenTree/templates/js/translated/build.js
index 5b416b4d22..201d3c5c9f 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);
diff --git a/InvenTree/templates/js/translated/label.js b/InvenTree/templates/js/translated/label.js
index 1c843917e6..c0f4c2f735 100644
--- a/InvenTree/templates/js/translated/label.js
+++ b/InvenTree/templates/js/translated/label.js
@@ -14,11 +14,41 @@
*/
/* 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 +87,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 +106,7 @@ function printStockItemLabels(items) {
);
}
+
function printStockLocationLabels(locations) {
if (locations.length == 0) {
@@ -107,14 +141,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 +199,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,18 +228,51 @@ 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);
+ // 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);
+ }
+ });
+ }
}
+ );
- return;
+ var plugin_selection = '';
+
+ if (plugins.length > 0) {
+ plugin_selection =`
+
+ `;
}
-
var modal = options.modal || '#modal-form';
var label_list = makeOptionsList(
@@ -233,14 +306,15 @@ function selectLabel(labels, items, options={}) {
`;
openModal({
@@ -255,14 +329,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/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/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/docker/requirements.txt b/docker/requirements.txt
index cd27bdb5ce..192d6dac6f 100644
--- a/docker/requirements.txt
+++ b/docker/requirements.txt
@@ -1,7 +1,7 @@
# Base python requirements for docker containers
# Basic package requirements
-setuptools>=57.4.0
+setuptools>=57.4.0,<=60.1.0
wheel>=0.37.0
invoke>=1.4.0 # Invoke build tool