mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 13:05:42 +00:00
Merge branch 'master' of https://github.com/inventree/InvenTree into matmair/issue2694
This commit is contained in:
@ -10,7 +10,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block actions %}
|
||||
<div class='btn btn-secondary' type='button' id='mark-all' title='{% trans "Mark all as read" %}'>
|
||||
<div class='btn btn-outline-secondary' type='button' id='mark-all' title='{% trans "Mark all as read" %}'>
|
||||
<span class='fa fa-bookmark'></span> {% trans "Mark all as read" %}
|
||||
</div>
|
||||
<div class='btn btn-secondary' type='button' id='inbox-refresh' title='{% trans "Refresh Pending Notifications" %}'>
|
||||
|
@ -126,8 +126,12 @@ $("#mark-all").on('click', function() {
|
||||
{
|
||||
read: false,
|
||||
},
|
||||
{
|
||||
success: function(response) {
|
||||
updateNotificationTables();
|
||||
}
|
||||
}
|
||||
);
|
||||
updateNotificationTables();
|
||||
});
|
||||
|
||||
loadNotificationTable("#history-table", {
|
||||
|
@ -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" %}
|
||||
|
@ -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 %}
|
||||
|
@ -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 %}
|
||||
|
||||
<div class='row'>
|
||||
<table class='table table-striped table-condensed'>
|
||||
<tbody>
|
||||
{% include "InvenTree/settings/setting.html" with key="NOTIFICATION_SEND_EMAILS" icon='fa-envelope' user_setting=True %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
@ -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);
|
||||
|
@ -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 =`
|
||||
<div class='form-group'>
|
||||
<label class='control-label requiredField' for='id_plugin'>
|
||||
{% trans "Select Printer" %}
|
||||
</label>
|
||||
<div class='controls'>
|
||||
<select id='id_plugin' class='select form-control' name='plugin'>
|
||||
<option value='' title='{% trans "Export to PDF" %}'>{% trans "Export to PDF" %}</option>
|
||||
`;
|
||||
|
||||
plugins.forEach(function(plugin) {
|
||||
plugin_selection += `<option value='${plugin.key}' title='${plugin.meta.human_name}'>${plugin.name} - <small>${plugin.meta.human_name}</small></option>`;
|
||||
});
|
||||
|
||||
plugin_selection += `
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
var modal = options.modal || '#modal-form';
|
||||
|
||||
var label_list = makeOptionsList(
|
||||
@ -233,14 +306,15 @@ function selectLabel(labels, items, options={}) {
|
||||
<form method='post' action='' class='js-modal-form' enctype='multipart/form-data'>
|
||||
<div class='form-group'>
|
||||
<label class='control-label requiredField' for='id_label'>
|
||||
{% trans "Select Label" %}
|
||||
{% trans "Select Label Template" %}
|
||||
</label>
|
||||
<div class='controls'>
|
||||
<select id='id_label' class='select form-control name='label'>
|
||||
<select id='id_label' class='select form-control' name='label'>
|
||||
${label_list}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
${plugin_selection}
|
||||
</form>`;
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -253,7 +253,7 @@ function openNotificationPanel() {
|
||||
{
|
||||
success: function(response) {
|
||||
if (response.length == 0) {
|
||||
html = `<p class='text-muted'>{% trans "No unread notifications" %}</p>`;
|
||||
html = `<p class='text-muted'><em>{% trans "No unread notifications" %}</em><span class='fas fa-check-circle icon-green float-right'></span></p>`;
|
||||
} else {
|
||||
// build up items
|
||||
response.forEach(function(item, index) {
|
||||
|
@ -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');
|
||||
|
||||
|
Reference in New Issue
Block a user