2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-17 12:35:46 +00:00

Merge branch 'inventree:master' into plugin-mark-sample

This commit is contained in:
Matthias Mair
2022-04-19 18:24:21 +02:00
committed by GitHub
68 changed files with 20506 additions and 11625 deletions

View File

@ -24,6 +24,7 @@
{% include "InvenTree/settings/setting.html" with key="ENABLE_PLUGINS_URL" icon="fa-link" %}
{% include "InvenTree/settings/setting.html" with key="ENABLE_PLUGINS_NAVIGATION" icon="fa-sitemap" %}
{% include "InvenTree/settings/setting.html" with key="ENABLE_PLUGINS_APP" icon="fa-rocket" %}
{% include "InvenTree/settings/setting.html" with key="PLUGIN_ON_STARTUP" %}
</tbody>
</table>
</div>

View File

@ -89,7 +89,7 @@ $('table').find('.boolean-setting').change(function() {
},
{
method: 'PATCH',
onSuccess: function(data) {
success: function(data) {
},
error: function(xhr) {
showApiError(xhr, url);

View File

@ -48,6 +48,7 @@
<link rel="stylesheet" href="{% static 'select2/css/select2-bootstrap-5-theme.css' %}">
<link rel="stylesheet" href="{% static 'fullcalendar/main.css' %}">
<link rel="stylesheet" href="{% static 'script/jquery-ui/jquery-ui.min.css' %}">
<link rel="stylesheet" href="{% static 'easymde/easymde.min.css' %}">
<link rel="stylesheet" href="{% static 'css/inventree.css' %}">
@ -160,6 +161,7 @@
<script type='text/javascript' src="{% static 'script/chart.js' %}"></script>
<script type='text/javascript' src="{% static 'script/moment.js' %}"></script>
<script type='text/javascript' src="{% static 'script/chartjs-adapter-moment.js' %}"></script>
<script type='text/javascript' src="{% static 'easymde/easymde.min.js' %}"></script>
<script type='text/javascript' src="{% static 'script/clipboard.min.js' %}"></script>
<script type='text/javascript' src="{% static 'script/randomColor.min.js' %}"></script>

View File

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

View File

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

View File

@ -798,17 +798,25 @@ function loadBomTable(table, options={}) {
});
cols.push({
field: 'sub_part_detail.stock',
field: 'available_stock',
title: '{% trans "Available" %}',
searchable: false,
sortable: true,
formatter: function(value, row) {
var url = `/part/${row.sub_part_detail.pk}/?display=part-stock`;
var text = value;
if (value == null || value <= 0) {
text = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock" %}</span>`;
// Calculate total "available" (unallocated) quantity
var total = row.available_stock + row.available_substitute_stock;
var text = `${total}`;
if (total <= 0) {
text = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock Available" %}</span>`;
} else {
if (row.available_substitute_stock > 0) {
text += `<span title='{% trans "Includes substitute stock" %}' class='fas fa-info-circle float-right icon-blue'></span>`;
}
}
return renderLink(text, url);
@ -902,8 +910,10 @@ function loadBomTable(table, options={}) {
formatter: function(value, row) {
var can_build = 0;
var available = row.available_stock + row.available_substitute_stock;
if (row.quantity > 0) {
can_build = row.sub_part_detail.stock / row.quantity;
can_build = available / row.quantity;
}
return +can_build.toFixed(2);
@ -914,11 +924,11 @@ function loadBomTable(table, options={}) {
var cb_b = 0;
if (rowA.quantity > 0) {
cb_a = rowA.sub_part_detail.stock / rowA.quantity;
cb_a = (rowA.available_stock + rowA.available_substitute_stock) / rowA.quantity;
}
if (rowB.quantity > 0) {
cb_b = rowB.sub_part_detail.stock / rowB.quantity;
cb_b = (rowB.available_stock + rowB.available_substitute_stock) / rowB.quantity;
}
return (cb_a > cb_b) ? 1 : -1;

View File

@ -1421,9 +1421,24 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
sortable: true,
},
{
field: 'sub_part_detail.stock',
field: 'available_stock',
title: '{% trans "Available" %}',
sortable: true,
formatter: function(value, row) {
var total = row.available_stock + row.available_substitute_stock;
var text = `${total}`;
if (total <= 0) {
text = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock Available" %}</span>`;
} else {
if (row.available_substitute_stock > 0) {
text += `<span title='{% trans "Includes substitute stock" %}' class='fas fa-info-circle float-right icon-blue'></span>`;
}
}
return text;
}
},
{
field: 'allocated',

View File

@ -10,6 +10,7 @@
makeProgressBar,
renderLink,
select2Thumbnail,
setupNotesField,
thumbnailImage
yesNoLabel,
*/
@ -221,3 +222,93 @@ function renderLink(text, url, options={}) {
return `<a href="${url}">${text}</a>`;
}
function setupNotesField(element, url, options={}) {
var editable = options.editable || false;
// Read initial notes value from the URL
var initial = null;
inventreeGet(url, {}, {
async: false,
success: function(response) {
initial = response[options.notes_field || 'notes'];
},
});
var toolbar_icons = [
'preview', '|',
];
if (editable) {
// Heading icons
toolbar_icons.push('heading-1', 'heading-2', 'heading-3', '|');
// Font style
toolbar_icons.push('bold', 'italic', 'strikethrough', '|');
// Text formatting
toolbar_icons.push('unordered-list', 'ordered-list', 'code', 'quote', '|');
// Elements
toolbar_icons.push('table', 'link', 'image');
}
// Markdown syntax guide
toolbar_icons.push('|', 'guide');
const mde = new EasyMDE({
element: document.getElementById(element),
initialValue: initial,
toolbar: toolbar_icons,
shortcuts: [],
});
// Hide the toolbar
$(`#${element}`).next('.EasyMDEContainer').find('.editor-toolbar').hide();
if (!editable) {
// Set readonly
mde.codemirror.setOption('readOnly', true);
// Hide the "edit" and "save" buttons
$('#edit-notes').hide();
$('#save-notes').hide();
} else {
mde.togglePreview();
// Add callback for "edit" button
$('#edit-notes').click(function() {
$('#edit-notes').hide();
$('#save-notes').show();
// Show the toolbar
$(`#${element}`).next('.EasyMDEContainer').find('.editor-toolbar').show();
mde.togglePreview();
});
// Add callback for "save" button
$('#save-notes').click(function() {
var data = {};
data[options.notes_field || 'notes'] = mde.value();
inventreePut(url, data, {
method: 'PATCH',
success: function(response) {
showMessage('{% trans "Notes updated" %}', {style: 'success'});
},
error: function(xhr) {
showApiError(xhr, url);
}
});
});
}
}

View File

@ -34,8 +34,8 @@
// Should the ID be rendered for this string
function renderId(title, pk, parameters={}) {
// Default = true
var render = true;
// Default = do not display
var render = false;
if ('render_pk' in parameters) {
render = parameters['render_pk'];
@ -99,14 +99,22 @@ function renderStockItem(name, data, parameters={}, options={}) {
var stock_detail = '';
if (data.serial && data.quantity == 1) {
stock_detail = `{% trans "Serial Number" %}: ${data.serial}`;
} else if (data.quantity == 0) {
if (data.quantity == 0) {
stock_detail = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock"% }</span>`;
} else {
stock_detail = `{% trans "Quantity" %}: ${data.quantity}`;
if (data.serial && data.quantity == 1) {
stock_detail = `{% trans "Serial Number" %}: ${data.serial}`;
} else {
stock_detail = `{% trans "Quantity" %}: ${data.quantity}`;
}
if (data.batch) {
stock_detail += ` - <small>{% trans "Batch" %}: ${data.batch}</small>`;
}
}
var html = `
<span>
${part_detail}
@ -192,7 +200,7 @@ function renderPart(name, data, parameters={}, options={}) {
<small>
${stock_data}
${extra}
${renderId('{% trans "Part ID" $}', data.pk, parameters)}
${renderId('{% trans "Part ID" %}', data.pk, parameters)}
</small>
</span>`;

View File

@ -171,6 +171,9 @@ function notificationCheck(force = false) {
{
success: function(response) {
updateNotificationIndicator(response.length);
},
error: function(xhr) {
console.warn('Could not access server: /api/notifications');
}
}
);

View File

@ -293,6 +293,7 @@ function categoryFields() {
return {
parent: {
help_text: '{% trans "Parent part category" %}',
required: false,
},
name: {},
description: {},
@ -373,6 +374,9 @@ function duplicatePart(pk, options={}) {
// Override the "variant_of" field
data.variant_of = pk;
// By default, disable "is_template" when making a variant *of* a template
data.is_template = false;
}
constructForm('{% url "api-part-list" %}', {

View File

@ -89,7 +89,8 @@ function updateSearch() {
var params = {};
if (user_settings.SEARCH_HIDE_INACTIVE_PARTS) {
params.active = false;
// Return *only* active parts
params.active = true;
}
// Search for matching parts

View File

@ -107,6 +107,7 @@ function stockLocationFields(options={}) {
var fields = {
parent: {
help_text: '{% trans "Parent stock location" %}',
required: false,
},
name: {},
description: {},
@ -240,9 +241,11 @@ function stockItemFields(options={}) {
serial: {
icon: 'fa-hashtag',
},
batch: {
icon: 'fa-layer-group',
},
status: {},
expiry_date: {},
batch: {},
purchase_price: {
icon: 'fa-dollar-sign',
},
@ -963,6 +966,10 @@ function adjustStock(action, items, options={}) {
quantity = `#${item.serial}`;
}
if (item.batch) {
quantity += ` - <small>{% trans "Batch" %}: ${item.batch}</small>`;
}
var actionInput = '';
if (actionTitle != null) {
@ -1331,14 +1338,27 @@ function loadStockTestResultsTable(table, options) {
});
// Once the test template data are loaded, query for test results
var filters = loadTableFilters(filterKey);
var query_params = {
stock_item: options.stock_item,
user_detail: true,
attachment_detail: true,
ordering: '-date',
};
if ('result' in filters) {
query_params.result = filters.result;
}
if ('include_installed' in filters) {
query_params.include_installed = filters.include_installed;
}
inventreeGet(
'{% url "api-stock-test-result-list" %}',
{
stock_item: options.stock_item,
user_detail: true,
attachment_detail: true,
ordering: '-date',
},
query_params,
{
success: function(data) {
// Iterate through the returned test data
@ -1972,7 +1992,7 @@ function loadStockTable(table, options) {
var items = [];
selections.forEach(function(item) {
items.push(item.pk);
items.push(item);
});
scanItemsIntoLocation(items);
@ -2301,6 +2321,23 @@ function loadStockTrackingTable(table, options) {
var cols = [];
var filterTarget = '#filter-list-stocktracking';
var filterKey = 'stocktracking';
var filters = loadTableFilters(filterKey);
var params = options.params;
var original = {};
for (var k in params) {
original[k] = params[k];
filters[k] = params[k];
}
setupFilterList(filterKey, table, filterTarget);
// Date
cols.push({
field: 'date',
@ -2338,6 +2375,19 @@ function loadStockTrackingTable(table, options) {
return html;
}
// Part information
if (details.part) {
html += `<tr><th>{% trans "Part" %}</th><td>`;
if (details.part_detail) {
html += renderLink(details.part_detail.full_name, `/part/${details.part}/`);
} else {
html += `{% trans "Part information unavailable" %}`;
}
html += `</td></tr>`;
}
// Location information
if (details.location) {
@ -2475,27 +2525,10 @@ function loadStockTrackingTable(table, options) {
}
});
/*
// 2021-05-11 - Ability to edit or delete StockItemTracking entries is now removed
cols.push({
sortable: false,
formatter: function(value, row, index, field) {
// Manually created entries can be edited or deleted
if (false && !row.system) {
var bEdit = "<button title='{% trans 'Edit tracking entry' %}' class='btn btn-entry-edit btn-outline-secondary' type='button' url='/stock/track/" + row.pk + "/edit/'><span class='fas fa-edit'/></button>";
var bDel = "<button title='{% trans 'Delete tracking entry' %}' class='btn btn-entry-delete btn-outline-secondary' type='button' url='/stock/track/" + row.pk + "/delete/'><span class='fas fa-trash-alt icon-red'/></button>";
return "<div class='btn-group' role='group'>" + bEdit + bDel + "</div>";
} else {
return "";
}
}
});
*/
table.inventreeTable({
method: 'get',
queryParams: options.params,
queryParams: filters,
original: original,
columns: cols,
url: options.url,
});
@ -2626,7 +2659,8 @@ function installStockItem(stock_item_id, part_id, options={}) {
<ul>
<li>{% trans "The Stock Item links to a Part which is the BOM for this Stock Item" %}</li>
<li>{% trans "The Stock Item is currently available in stock" %}</li>
<li>{% trans "The Stock Item is serialized and does not belong to another item" %}</li>
<li>{% trans "The Stock Item is not already installed in another item" %}</li>
<li>{% trans "The Stock Item is tracked by either a batch code or serial number" %}</li>
</ul>
</div>`;
@ -2652,7 +2686,7 @@ function installStockItem(stock_item_id, part_id, options={}) {
filters: {
part_detail: true,
in_stock: true,
serialized: true,
tracked: true,
},
adjustFilters: function(filters, opts) {
var part = getFormFieldValue('part', {}, opts);

View File

@ -234,10 +234,19 @@ function getAvailableTableFilters(tableKey) {
title: '{% trans "Stock status" %}',
description: '{% trans "Stock status" %}',
},
has_batch: {
title: '{% trans "Has batch code" %}',
type: 'bool',
},
batch: {
title: '{% trans "Batch" %}',
description: '{% trans "Batch code" %}',
},
tracked: {
title: '{% trans "Tracked" %}',
description: '{% trans "Stock item is tracked by either batch code or serial number" %}',
type: 'bool',
},
has_purchase_price: {
type: 'bool',
title: '{% trans "Has purchase price" %}',
@ -265,7 +274,16 @@ function getAvailableTableFilters(tableKey) {
// Filters for the 'stock test' table
if (tableKey == 'stocktests') {
return {};
return {
result: {
type: 'bool',
title: '{% trans "Test Passed" %}',
},
include_installed: {
type: 'bool',
title: '{% trans "Include Installed Items" %}',
}
};
}
// Filters for the 'part test template' table

View File

@ -0,0 +1,8 @@
{% load i18n %}
<button type='button' id='edit-notes' title='{% trans "Edit" %}' class='btn btn-primary'>
<span class='fas fa-edit'></span> {% trans "Edit" %}
</button>
<button type='button' id='save-notes' title='{% trans "Save" %}' class='btn btn-success' style='display: none;'>
<span class='fas fa-save'></span> {% trans "Save" %}
</button>

View File

@ -1,3 +1,5 @@
{% load i18n %}
<div class='panel' id='{{ panel_id }}'>
<div class='panel-heading'>
<h4>