mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 13:05:42 +00:00
Merge branch 'inventree:master' into matmair/issue2279
This commit is contained in:
@ -671,9 +671,7 @@ function loadBomTable(table, options={}) {
|
||||
// Do we show part pricing in the BOM table?
|
||||
var show_pricing = global_settings.PART_SHOW_PRICE_IN_BOM;
|
||||
|
||||
if (!show_pricing) {
|
||||
params.include_pricing = false;
|
||||
}
|
||||
params.include_pricing = show_pricing == true;
|
||||
|
||||
if (options.part_detail) {
|
||||
params.part_detail = true;
|
||||
@ -989,32 +987,40 @@ function loadBomTable(table, options={}) {
|
||||
|
||||
// Function to request BOM data for sub-items
|
||||
// This function may be called recursively for multi-level BOMs
|
||||
function requestSubItems(bom_pk, part_pk) {
|
||||
function requestSubItems(bom_pk, part_pk, depth=0) {
|
||||
|
||||
// TODO: 2022-02-03 Currently, multi-level BOMs are not actually displayed.
|
||||
// Prevent multi-level recursion
|
||||
const MAX_BOM_DEPTH = 25;
|
||||
|
||||
// Re-enable this function once multi-level display has been re-deployed
|
||||
return;
|
||||
if (depth >= MAX_BOM_DEPTH) {
|
||||
console.log(`Maximum BOM depth (${MAX_BOM_DEPTH}) reached!`);
|
||||
return;
|
||||
}
|
||||
|
||||
inventreeGet(
|
||||
options.bom_url,
|
||||
{
|
||||
part: part_pk,
|
||||
sub_part_detail: true,
|
||||
include_pricing: show_pricing == true,
|
||||
},
|
||||
{
|
||||
success: function(response) {
|
||||
|
||||
// Add the returned sub-items to the table
|
||||
for (var idx = 0; idx < response.length; idx++) {
|
||||
|
||||
response[idx].parentId = bom_pk;
|
||||
|
||||
if (response[idx].sub_part_detail.assembly) {
|
||||
requestSubItems(response[idx].pk, response[idx].sub_part);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
table.bootstrapTable('append', response);
|
||||
|
||||
// Next, re-iterate and check if the new items also have sub items
|
||||
response.forEach(function(bom_item) {
|
||||
if (bom_item.sub_part_detail.assembly) {
|
||||
requestSubItems(bom_item.pk, bom_item.sub_part, depth + 1);
|
||||
}
|
||||
});
|
||||
|
||||
table.treegrid('collapseAll');
|
||||
},
|
||||
error: function(xhr) {
|
||||
@ -1026,7 +1032,7 @@ function loadBomTable(table, options={}) {
|
||||
}
|
||||
|
||||
table.inventreeTable({
|
||||
treeEnable: !options.editable,
|
||||
treeEnable: true,
|
||||
rootParentId: parent_id,
|
||||
idField: 'pk',
|
||||
uniqueId: 'pk',
|
||||
@ -1066,38 +1072,37 @@ function loadBomTable(table, options={}) {
|
||||
url: options.bom_url,
|
||||
onPostBody: function() {
|
||||
|
||||
if (!options.editable) {
|
||||
table.treegrid({
|
||||
treeColumn: 0,
|
||||
onExpand: function() {
|
||||
}
|
||||
});
|
||||
}
|
||||
table.treegrid({
|
||||
treeColumn: 1,
|
||||
onExpand: function() {
|
||||
}
|
||||
});
|
||||
|
||||
table.treegrid('collapseAll');
|
||||
},
|
||||
onLoadSuccess: function() {
|
||||
|
||||
if (options.editable) {
|
||||
table.bootstrapTable('uncheckAll');
|
||||
} else {
|
||||
}
|
||||
|
||||
var data = table.bootstrapTable('getData');
|
||||
var data = table.bootstrapTable('getData');
|
||||
|
||||
for (var idx = 0; idx < data.length; idx++) {
|
||||
var row = data[idx];
|
||||
for (var idx = 0; idx < data.length; idx++) {
|
||||
var row = data[idx];
|
||||
|
||||
// If a row already has a parent ID set, it's already been updated!
|
||||
if (row.parentId) {
|
||||
continue;
|
||||
}
|
||||
// If a row already has a parent ID set, it's already been updated!
|
||||
if (row.parentId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set the parent ID of the top-level rows
|
||||
row.parentId = parent_id;
|
||||
// Set the parent ID of the top-level rows
|
||||
row.parentId = parent_id;
|
||||
|
||||
table.bootstrapTable('updateRow', idx, row, true);
|
||||
table.bootstrapTable('updateRow', idx, row, true);
|
||||
|
||||
if (row.sub_part_detail.assembly) {
|
||||
requestSubItems(row.pk, row.sub_part);
|
||||
}
|
||||
if (row.sub_part_detail.assembly) {
|
||||
requestSubItems(row.pk, row.sub_part);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -256,7 +256,7 @@ function generateFilterInput(tableKey, filterKey) {
|
||||
* @param {*} table - bootstrapTable element to update
|
||||
* @param {*} target - name of target element on page
|
||||
*/
|
||||
function setupFilterList(tableKey, table, target) {
|
||||
function setupFilterList(tableKey, table, target, options={}) {
|
||||
|
||||
var addClicked = false;
|
||||
|
||||
@ -283,6 +283,11 @@ function setupFilterList(tableKey, table, target) {
|
||||
|
||||
var buttons = '';
|
||||
|
||||
// Add download button
|
||||
if (options.download) {
|
||||
buttons += `<button id='download-${tableKey}' title='{% trans "Download data" %}' class='btn btn-outline-secondary filter-button'><span class='fas fa-download'></span></button>`;
|
||||
}
|
||||
|
||||
buttons += `<button id='reload-${tableKey}' title='{% trans "Reload data" %}' class='btn btn-outline-secondary filter-button'><span class='fas fa-redo-alt'></span></button>`;
|
||||
|
||||
// If there are filters defined for this table, add more buttons
|
||||
@ -295,7 +300,7 @@ function setupFilterList(tableKey, table, target) {
|
||||
}
|
||||
|
||||
element.html(`
|
||||
<div class='btn-group' role='group'>
|
||||
<div class='btn-group filter-group' role='group'>
|
||||
${buttons}
|
||||
</div>
|
||||
`);
|
||||
@ -322,6 +327,13 @@ function setupFilterList(tableKey, table, target) {
|
||||
$(table).bootstrapTable('refresh');
|
||||
});
|
||||
|
||||
// Add a callback for downloading table data
|
||||
if (options.download) {
|
||||
element.find(`#download-${tableKey}`).click(function() {
|
||||
downloadTableData($(table));
|
||||
});
|
||||
}
|
||||
|
||||
// Add a callback for adding a new filter
|
||||
element.find(`#${add}`).click(function clicked() {
|
||||
|
||||
@ -358,14 +370,14 @@ function setupFilterList(tableKey, table, target) {
|
||||
reloadTableFilters(table, filters);
|
||||
|
||||
// Run this function again
|
||||
setupFilterList(tableKey, table, target);
|
||||
setupFilterList(tableKey, table, target, options);
|
||||
}
|
||||
|
||||
});
|
||||
} else {
|
||||
addClicked = false;
|
||||
|
||||
setupFilterList(tableKey, table, target);
|
||||
setupFilterList(tableKey, table, target, options);
|
||||
}
|
||||
|
||||
});
|
||||
@ -376,7 +388,7 @@ function setupFilterList(tableKey, table, target) {
|
||||
|
||||
reloadTableFilters(table, filters);
|
||||
|
||||
setupFilterList(tableKey, table, target);
|
||||
setupFilterList(tableKey, table, target, options);
|
||||
});
|
||||
|
||||
// Add callback for deleting each filter
|
||||
@ -390,7 +402,7 @@ function setupFilterList(tableKey, table, target) {
|
||||
reloadTableFilters(table, filters);
|
||||
|
||||
// Run this function again!
|
||||
setupFilterList(tableKey, table, target);
|
||||
setupFilterList(tableKey, table, target, options);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -281,6 +281,65 @@ function createPurchaseOrder(options={}) {
|
||||
}
|
||||
|
||||
|
||||
/* Construct a set of fields for the SalesOrderLineItem form */
|
||||
function soLineItemFields(options={}) {
|
||||
|
||||
var fields = {
|
||||
order: {
|
||||
hidden: true,
|
||||
},
|
||||
part: {},
|
||||
quantity: {},
|
||||
reference: {},
|
||||
sale_price: {},
|
||||
sale_price_currency: {},
|
||||
target_date: {},
|
||||
notes: {},
|
||||
};
|
||||
|
||||
if (options.order) {
|
||||
fields.order.value = options.order;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
|
||||
/* Construct a set of fields for the PurchaseOrderLineItem form */
|
||||
function poLineItemFields(options={}) {
|
||||
|
||||
var fields = {
|
||||
order: {
|
||||
hidden: true,
|
||||
},
|
||||
part: {
|
||||
filters: {
|
||||
part_detail: true,
|
||||
supplier_detail: true,
|
||||
supplier: options.supplier,
|
||||
}
|
||||
},
|
||||
quantity: {},
|
||||
reference: {},
|
||||
purchase_price: {},
|
||||
purchase_price_currency: {},
|
||||
target_date: {},
|
||||
destination: {},
|
||||
notes: {},
|
||||
};
|
||||
|
||||
if (options.order) {
|
||||
fields.order.value = options.order;
|
||||
}
|
||||
|
||||
if (options.currency) {
|
||||
fields.purchase_price_currency.value = options.currency;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
|
||||
function removeOrderRowFromOrderWizard(e) {
|
||||
/* Remove a part selection from an order form. */
|
||||
|
||||
@ -293,6 +352,7 @@ function removeOrderRowFromOrderWizard(e) {
|
||||
$('#' + row).remove();
|
||||
}
|
||||
|
||||
|
||||
function newSupplierPartFromOrderWizard(e) {
|
||||
/* Create a new supplier part directly from an order form.
|
||||
* Launches a secondary modal and (if successful),
|
||||
@ -991,10 +1051,36 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
|
||||
|
||||
var target = options.filter_target || '#filter-list-purchase-order-lines';
|
||||
|
||||
setupFilterList('purchaseorderlineitem', $(table), target);
|
||||
setupFilterList('purchaseorderlineitem', $(table), target, {download: true});
|
||||
|
||||
function setupCallbacks() {
|
||||
if (options.allow_edit) {
|
||||
|
||||
// Callback for "duplicate" button
|
||||
$(table).find('.button-line-duplicate').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
inventreeGet(`/api/order/po-line/${pk}/`, {}, {
|
||||
success: function(data) {
|
||||
|
||||
var fields = poLineItemFields({
|
||||
supplier: options.supplier,
|
||||
});
|
||||
|
||||
constructForm('{% url "api-po-line-list" %}', {
|
||||
method: 'POST',
|
||||
fields: fields,
|
||||
data: data,
|
||||
title: '{% trans "Duplicate Line Item" %}',
|
||||
onSuccess: function(response) {
|
||||
$(table).bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Callback for "edit" button
|
||||
$(table).find('.button-line-edit').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
@ -1022,6 +1108,7 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
|
||||
});
|
||||
});
|
||||
|
||||
// Callback for "delete" button
|
||||
$(table).find('.button-line-delete').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
@ -1270,6 +1357,7 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
|
||||
}
|
||||
|
||||
if (options.allow_edit) {
|
||||
html += makeIconButton('fa-clone', 'button-line-duplicate', pk, '{% trans "Duplicate line item" %}');
|
||||
html += makeIconButton('fa-edit icon-blue', 'button-line-edit', pk, '{% trans "Edit line item" %}');
|
||||
html += makeIconButton('fa-trash-alt icon-red', 'button-line-delete', pk, '{% trans "Delete line item" %}');
|
||||
}
|
||||
@ -2449,6 +2537,7 @@ function loadSalesOrderLineItemTable(table, options={}) {
|
||||
html += makeIconButton('fa-dollar-sign icon-green', 'button-price', pk, '{% trans "Calculate price" %}');
|
||||
}
|
||||
|
||||
html += makeIconButton('fa-clone', 'button-duplicate', pk, '{% trans "Duplicate line item" %}');
|
||||
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line item" %}');
|
||||
|
||||
var delete_disabled = false;
|
||||
@ -2480,6 +2569,28 @@ function loadSalesOrderLineItemTable(table, options={}) {
|
||||
// Configure callback functions once the table is loaded
|
||||
function setupCallbacks() {
|
||||
|
||||
// Callback for duplicating line items
|
||||
$(table).find('.button-duplicate').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
inventreeGet(`/api/order/so-line/${pk}/`, {}, {
|
||||
success: function(data) {
|
||||
|
||||
var fields = soLineItemFields();
|
||||
|
||||
constructForm('{% url "api-so-line-list" %}', {
|
||||
method: 'POST',
|
||||
fields: fields,
|
||||
data: data,
|
||||
title: '{% trans "Duplicate Line Item" %}',
|
||||
onSuccess: function(response) {
|
||||
$(table).bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Callback for editing line items
|
||||
$(table).find('.button-edit').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
@ -474,6 +474,7 @@ function duplicateBom(part_id, options={}) {
|
||||
}
|
||||
},
|
||||
include_inherited: {},
|
||||
copy_substitutes: {},
|
||||
remove_existing: {},
|
||||
skip_invalid: {},
|
||||
},
|
||||
@ -1217,7 +1218,7 @@ function loadPartTable(table, url, options={}) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
|
||||
setupFilterList('parts', $(table), options.filterTarget || null);
|
||||
setupFilterList('parts', $(table), options.filterTarget, {download: true});
|
||||
|
||||
var columns = [
|
||||
{
|
||||
|
@ -43,7 +43,6 @@
|
||||
duplicateStockItem,
|
||||
editStockItem,
|
||||
editStockLocation,
|
||||
exportStock,
|
||||
findStockItemBySerialNumber,
|
||||
installStockItem,
|
||||
loadInstalledInTable,
|
||||
@ -506,49 +505,6 @@ function stockStatusCodes() {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Export stock table
|
||||
*/
|
||||
function exportStock(params={}) {
|
||||
|
||||
constructFormBody({}, {
|
||||
title: '{% trans "Export Stock" %}',
|
||||
fields: {
|
||||
format: {
|
||||
label: '{% trans "Format" %}',
|
||||
help_text: '{% trans "Select file format" %}',
|
||||
required: true,
|
||||
type: 'choice',
|
||||
value: 'csv',
|
||||
choices: exportFormatOptions(),
|
||||
},
|
||||
sublocations: {
|
||||
label: '{% trans "Include Sublocations" %}',
|
||||
help_text: '{% trans "Include stock items in sublocations" %}',
|
||||
type: 'boolean',
|
||||
value: 'true',
|
||||
}
|
||||
},
|
||||
onSubmit: function(fields, form_options) {
|
||||
|
||||
var format = getFormFieldValue('format', fields['format'], form_options);
|
||||
var cascade = getFormFieldValue('sublocations', fields['sublocations'], form_options);
|
||||
|
||||
// Hide the modal
|
||||
$(form_options.modal).modal('hide');
|
||||
|
||||
var url = `{% url "stock-export" %}?format=${format}&cascade=${cascade}`;
|
||||
|
||||
for (var key in params) {
|
||||
url += `&${key}=${params[key]}`;
|
||||
}
|
||||
|
||||
location.href = url;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Assign multiple stock items to a customer
|
||||
*/
|
||||
@ -1615,7 +1571,7 @@ function loadStockTable(table, options) {
|
||||
original[k] = params[k];
|
||||
}
|
||||
|
||||
setupFilterList(filterKey, table, filterTarget);
|
||||
setupFilterList(filterKey, table, filterTarget, {download: true});
|
||||
|
||||
// Override the default values, or add new ones
|
||||
for (var key in params) {
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
/* exported
|
||||
customGroupSorter,
|
||||
downloadTableData,
|
||||
reloadtable,
|
||||
renderLink,
|
||||
reloadTableFilters,
|
||||
@ -21,6 +22,62 @@ function reloadtable(table) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Download data from a table, via the API.
|
||||
* This requires a number of conditions to be met:
|
||||
*
|
||||
* - The API endpoint supports data download (on the server side)
|
||||
* - The table is "flat" (does not support multi-level loading, etc)
|
||||
* - The table has been loaded using the inventreeTable() function, not bootstrapTable()
|
||||
* (Refer to the "reloadTableFilters" function to see why!)
|
||||
*/
|
||||
function downloadTableData(table, opts={}) {
|
||||
|
||||
// Extract table configuration options
|
||||
var table_options = table.bootstrapTable('getOptions');
|
||||
|
||||
var url = table_options.url;
|
||||
|
||||
if (!url) {
|
||||
console.log('Error: downloadTableData could not find "url" parameter.');
|
||||
}
|
||||
|
||||
var query_params = table_options.query_params || {};
|
||||
|
||||
url += '?';
|
||||
|
||||
constructFormBody({}, {
|
||||
title: opts.title || '{% trans "Export Table Data" %}',
|
||||
fields: {
|
||||
format: {
|
||||
label: '{% trans "Format" %}',
|
||||
help_text: '{% trans "Select File Format" %}',
|
||||
required: true,
|
||||
type: 'choice',
|
||||
value: 'csv',
|
||||
choices: exportFormatOptions(),
|
||||
}
|
||||
},
|
||||
onSubmit: function(fields, form_options) {
|
||||
var format = getFormFieldValue('format', fields['format'], form_options);
|
||||
|
||||
// Hide the modal
|
||||
$(form_options.modal).modal('hide');
|
||||
|
||||
for (const [key, value] of Object.entries(query_params)) {
|
||||
url += `${key}=${value}&`;
|
||||
}
|
||||
|
||||
url += `export=${format}`;
|
||||
|
||||
location.href = url;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Render a URL for display
|
||||
* @param {String} text
|
||||
@ -114,6 +171,10 @@ function reloadTableFilters(table, filters) {
|
||||
}
|
||||
}
|
||||
|
||||
// Store the total set of query params
|
||||
// This is necessary for the "downloadTableData" function to work
|
||||
options.query_params = params;
|
||||
|
||||
options.queryParams = function(tableParams) {
|
||||
return convertQueryParameters(tableParams, params);
|
||||
};
|
||||
@ -221,7 +282,11 @@ $.fn.inventreeTable = function(options) {
|
||||
// Extract query params
|
||||
var filters = options.queryParams || options.filters || {};
|
||||
|
||||
// Store the total set of query params
|
||||
options.query_params = filters;
|
||||
|
||||
options.queryParams = function(params) {
|
||||
// Update the query parameters callback with the *new* filters
|
||||
return convertQueryParameters(params, filters);
|
||||
};
|
||||
|
||||
|
@ -11,9 +11,6 @@
|
||||
<div id='{{ prefix }}button-toolbar'>
|
||||
<div class='button-toolbar container-fluid' style='float: right;'>
|
||||
<div class='btn-group' role='group'>
|
||||
<button class='btn btn-outline-secondary' id='stock-export' title='{% trans "Export Stock Information" %}'>
|
||||
<span class='fas fa-download'></span>
|
||||
</button>
|
||||
{% if barcodes %}
|
||||
<!-- Barcode actions menu -->
|
||||
<div class='btn-group' role='group'>
|
||||
|
Reference in New Issue
Block a user