mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-01 19:20:55 +00:00
Merge remote-tracking branch 'inventree/master' into order-parts-wizard
# Conflicts: # InvenTree/templates/js/translated/model_renderers.js
This commit is contained in:
@ -798,17 +798,38 @@ 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 base_stock = row.available_stock;
|
||||
var substitute_stock = row.available_substitute_stock || 0;
|
||||
var variant_stock = row.allow_variants ? (row.available_variant_stock || 0) : 0;
|
||||
|
||||
var available_stock = base_stock + substitute_stock + variant_stock;
|
||||
|
||||
var text = `${available_stock}`;
|
||||
|
||||
if (available_stock <= 0) {
|
||||
text = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock Available" %}</span>`;
|
||||
} else {
|
||||
var extra = '';
|
||||
if ((substitute_stock > 0) && (variant_stock > 0)) {
|
||||
extra = '{% trans "Includes variant and substitute stock" %}';
|
||||
} else if (variant_stock > 0) {
|
||||
extra = '{% trans "Includes variant stock" %}';
|
||||
} else if (substitute_stock > 0) {
|
||||
extra = '{% trans "Includes substitute stock" %}';
|
||||
}
|
||||
|
||||
if (extra) {
|
||||
text += `<span title='${extra}' class='fas fa-info-circle float-right icon-blue'></span>`;
|
||||
}
|
||||
}
|
||||
|
||||
return renderLink(text, url);
|
||||
@ -902,8 +923,10 @@ function loadBomTable(table, options={}) {
|
||||
formatter: function(value, row) {
|
||||
var can_build = 0;
|
||||
|
||||
var available = row.available_stock + (row.available_substitute_stock || 0) + (row.available_variant_stock || 0);
|
||||
|
||||
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 +937,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;
|
||||
|
@ -1421,9 +1421,41 @@ 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 url = `/part/${row.sub_part_detail.pk}/?display=part-stock`;
|
||||
|
||||
// Calculate total "available" (unallocated) quantity
|
||||
var base_stock = row.available_stock;
|
||||
var substitute_stock = row.available_substitute_stock || 0;
|
||||
var variant_stock = row.allow_variants ? (row.available_variant_stock || 0) : 0;
|
||||
|
||||
var available_stock = base_stock + substitute_stock + variant_stock;
|
||||
|
||||
var text = `${available_stock}`;
|
||||
|
||||
if (available_stock <= 0) {
|
||||
text = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock Available" %}</span>`;
|
||||
} else {
|
||||
var extra = '';
|
||||
if ((substitute_stock > 0) && (variant_stock > 0)) {
|
||||
extra = '{% trans "Includes variant and substitute stock" %}';
|
||||
} else if (variant_stock > 0) {
|
||||
extra = '{% trans "Includes variant stock" %}';
|
||||
} else if (substitute_stock > 0) {
|
||||
extra = '{% trans "Includes substitute stock" %}';
|
||||
}
|
||||
|
||||
if (extra) {
|
||||
text += `<span title='${extra}' class='fas fa-info-circle float-right icon-blue'></span>`;
|
||||
}
|
||||
}
|
||||
|
||||
return renderLink(text, url);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'allocated',
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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}
|
||||
@ -193,7 +201,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>`;
|
||||
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -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" %}', {
|
||||
@ -668,7 +672,20 @@ function loadPartVariantTable(table, partId, options={}) {
|
||||
field: 'in_stock',
|
||||
title: '{% trans "Stock" %}',
|
||||
formatter: function(value, row) {
|
||||
return renderLink(value, `/part/${row.pk}/?display=part-stock`);
|
||||
|
||||
var base_stock = row.in_stock;
|
||||
var variant_stock = row.variant_stock || 0;
|
||||
|
||||
var total = base_stock + variant_stock;
|
||||
|
||||
var text = `${total}`;
|
||||
|
||||
if (variant_stock > 0) {
|
||||
text = `<em>${text}</em>`;
|
||||
text += `<span title='{% trans "Includes variant stock" %}' class='fas fa-info-circle float-right icon-blue'></span>`;
|
||||
}
|
||||
|
||||
return renderLink(text, `/part/${row.pk}/?display=part-stock`);
|
||||
}
|
||||
}
|
||||
];
|
||||
@ -1900,7 +1917,9 @@ function loadPriceBreakTable(table, options) {
|
||||
formatNoMatches: function() {
|
||||
return `{% trans "No ${human_name} information found" %}`;
|
||||
},
|
||||
queryParams: {part: options.part},
|
||||
queryParams: {
|
||||
part: options.part
|
||||
},
|
||||
url: options.url,
|
||||
onLoadSuccess: function(tableData) {
|
||||
if (linkedGraph) {
|
||||
@ -2006,36 +2025,45 @@ function initPriceBreakSet(table, options) {
|
||||
}
|
||||
|
||||
pb_new_btn.click(function() {
|
||||
launchModalForm(pb_new_url,
|
||||
{
|
||||
success: reloadPriceBreakTable,
|
||||
data: {
|
||||
part: part_id,
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
constructForm(pb_new_url, {
|
||||
fields: {
|
||||
part: {
|
||||
hidden: true,
|
||||
value: part_id,
|
||||
},
|
||||
quantity: {},
|
||||
price: {},
|
||||
price_currency: {},
|
||||
},
|
||||
method: 'POST',
|
||||
title: '{% trans "Add Price Break" %}',
|
||||
onSuccess: reloadPriceBreakTable,
|
||||
});
|
||||
});
|
||||
|
||||
table.on('click', `.button-${pb_url_slug}-delete`, function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
launchModalForm(
|
||||
`/part/${pb_url_slug}/${pk}/delete/`,
|
||||
{
|
||||
success: reloadPriceBreakTable
|
||||
}
|
||||
);
|
||||
constructForm(`${pb_url}${pk}/`, {
|
||||
method: 'DELETE',
|
||||
title: '{% trans "Delete Price Break" %}',
|
||||
onSuccess: reloadPriceBreakTable,
|
||||
});
|
||||
});
|
||||
|
||||
table.on('click', `.button-${pb_url_slug}-edit`, function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
launchModalForm(
|
||||
`/part/${pb_url_slug}/${pk}/edit/`,
|
||||
{
|
||||
success: reloadPriceBreakTable
|
||||
}
|
||||
);
|
||||
constructForm(`${pb_url}${pk}/`, {
|
||||
fields: {
|
||||
quantity: {},
|
||||
price: {},
|
||||
price_currency: {},
|
||||
},
|
||||
title: '{% trans "Edit Price Break" %}',
|
||||
onSuccess: reloadPriceBreakTable,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
@ -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);
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user