mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 13:05:42 +00:00
Merge remote-tracking branch 'inventree/master' into scheduling
# Conflicts: # InvenTree/InvenTree/version.py (Update API version)
This commit is contained in:
@ -20,6 +20,7 @@
|
||||
|
||||
/* exported
|
||||
allocateStockToBuild,
|
||||
autoAllocateStockToBuild,
|
||||
completeBuildOrder,
|
||||
createBuildOutput,
|
||||
editBuildOrder,
|
||||
@ -754,7 +755,7 @@ function loadBuildOutputTable(build_info, options={}) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
|
||||
// TODO: Initialize filter list
|
||||
setupFilterList('builditems', $(table), options.filterTarget || '#filter-list-incompletebuilditems');
|
||||
|
||||
function setupBuildOutputButtonCallbacks() {
|
||||
|
||||
@ -999,7 +1000,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
|
||||
setupFilterList('builditems', $(table), options.filterTarget || null);
|
||||
setupFilterList('builditems', $(table), options.filterTarget);
|
||||
|
||||
// If an "output" is specified, then only "trackable" parts are allocated
|
||||
// Otherwise, only "untrackable" parts are allowed
|
||||
@ -1512,6 +1513,16 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
*/
|
||||
function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
||||
|
||||
if (bom_items.length == 0) {
|
||||
|
||||
showAlertDialog(
|
||||
'{% trans "Select Parts" %}',
|
||||
'{% trans "You must select at least one part to allocate" %}',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ID of the associated "build output" (or null)
|
||||
var output_id = options.output || null;
|
||||
|
||||
@ -1626,8 +1637,8 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
||||
if (table_entries.length == 0) {
|
||||
|
||||
showAlertDialog(
|
||||
'{% trans "Select Parts" %}',
|
||||
'{% trans "You must select at least one part to allocate" %}',
|
||||
'{% trans "All Parts Allocated" %}',
|
||||
'{% trans "All selected parts have been fully allocated" %}',
|
||||
);
|
||||
|
||||
return;
|
||||
@ -1844,6 +1855,48 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Automatically allocate stock items to a build
|
||||
*/
|
||||
function autoAllocateStockToBuild(build_id, bom_items=[], options={}) {
|
||||
|
||||
var html = `
|
||||
<div class='alert alert-block alert-info'>
|
||||
<strong>{% trans "Automatic Stock Allocation" %}</strong><br>
|
||||
{% trans "Stock items will be automatically allocated to this build order, according to the provided guidelines" %}:
|
||||
<ul>
|
||||
<li>{% trans "If a location is specifed, stock will only be allocated from that location" %}</li>
|
||||
<li>{% trans "If stock is considered interchangeable, it will be allocated from the first location it is found" %}</li>
|
||||
<li>{% trans "If substitute stock is allowed, it will be used where stock of the primary part cannot be found" %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
|
||||
var fields = {
|
||||
location: {
|
||||
value: options.location,
|
||||
},
|
||||
interchangeable: {
|
||||
value: true,
|
||||
},
|
||||
substitutes: {
|
||||
value: true,
|
||||
},
|
||||
};
|
||||
|
||||
constructForm(`/api/build/${build_id}/auto-allocate/`, {
|
||||
method: 'POST',
|
||||
fields: fields,
|
||||
title: '{% trans "Allocate Stock Items" %}',
|
||||
confirm: true,
|
||||
preFormContent: html,
|
||||
onSuccess: function(response) {
|
||||
$('#allocation-table-untracked').bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Display a table of Build orders
|
||||
*/
|
||||
|
@ -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),
|
||||
@ -899,7 +959,7 @@ function loadPurchaseOrderTable(table, options) {
|
||||
sortable: true,
|
||||
sortName: 'supplier__name',
|
||||
formatter: function(value, row) {
|
||||
return imageHoverIcon(row.supplier_detail.image) + renderLink(row.supplier_detail.name, `/company/${row.supplier}/purchase-orders/`);
|
||||
return imageHoverIcon(row.supplier_detail.image) + renderLink(row.supplier_detail.name, `/company/${row.supplier}/?display=purchase-orders`);
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -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');
|
||||
|
@ -1219,7 +1219,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