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:
@ -16,12 +16,6 @@
|
||||
<div class='panel panel-inventree'>
|
||||
<div class='panel-content'>
|
||||
{% include "search_form.html" with query_text=query %}
|
||||
{% if query %}
|
||||
{% else %}
|
||||
<div id='empty-search-query'>
|
||||
<h4><em>{% trans "Enter a search query" %}</em></h4>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<div id='attachment-buttons'>
|
||||
<div class='btn-group' role='group'>
|
||||
{% include "filter_list.html" with id="related" %}
|
||||
{% include "filter_list.html" with id="attachments" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -207,6 +207,11 @@ function showApiError(xhr, url) {
|
||||
title = '{% trans "Error 404: Resource Not Found" %}';
|
||||
message = '{% trans "The requested resource could not be located on the server" %}';
|
||||
break;
|
||||
// Method not allowed
|
||||
case 405:
|
||||
title = '{% trans "Error 405: Method Not Allowed" %}';
|
||||
message = '{% trans "HTTP method not allowed at URL" %}';
|
||||
break;
|
||||
// Timeout
|
||||
case 408:
|
||||
title = '{% trans "Error 408: Timeout" %}';
|
||||
|
@ -67,6 +67,8 @@ function loadAttachmentTable(url, options) {
|
||||
|
||||
var table = options.table || '#attachment-table';
|
||||
|
||||
setupFilterList('attachments', $(table), '#filter-list-attachments');
|
||||
|
||||
addAttachmentButtonCallbacks(url, options.fields || {});
|
||||
|
||||
$(table).inventreeTable({
|
||||
|
@ -661,7 +661,7 @@ function loadBomTable(table, options={}) {
|
||||
if (!row.inherited) {
|
||||
return yesNoLabel(false);
|
||||
} else if (row.part == options.parent_id) {
|
||||
return '{% trans "Inherited" %}';
|
||||
return yesNoLabel(true);
|
||||
} else {
|
||||
// If this BOM item is inherited from a parent part
|
||||
return renderLink(
|
||||
|
@ -380,6 +380,7 @@ function loadCompanyTable(table, url, options={}) {
|
||||
url: url,
|
||||
method: 'get',
|
||||
queryParams: filters,
|
||||
original: params,
|
||||
groupBy: false,
|
||||
sidePagination: 'server',
|
||||
formatNoMatches: function() {
|
||||
@ -463,7 +464,9 @@ function loadManufacturerPartTable(table, url, options) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
|
||||
setupFilterList('manufacturer-part', $(table));
|
||||
var filterTarget = options.filterTarget || '#filter-list-manufacturer-part';
|
||||
|
||||
setupFilterList('manufacturer-part', $(table), filterTarget);
|
||||
|
||||
$(table).inventreeTable({
|
||||
url: url,
|
||||
|
@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
/* exported
|
||||
duplicateBom,
|
||||
duplicatePart,
|
||||
editCategory,
|
||||
editPart,
|
||||
@ -39,6 +40,7 @@
|
||||
loadStockPricingChart,
|
||||
partStockLabel,
|
||||
toggleStar,
|
||||
validateBom,
|
||||
*/
|
||||
|
||||
/* Part API functions
|
||||
@ -428,6 +430,59 @@ function toggleStar(options) {
|
||||
}
|
||||
|
||||
|
||||
/* Validate a BOM */
|
||||
function validateBom(part_id, options={}) {
|
||||
|
||||
var html = `
|
||||
<div class='alert alert-block alert-success'>
|
||||
{% trans "Validating the BOM will mark each line item as valid" %}
|
||||
</div>
|
||||
`;
|
||||
|
||||
constructForm(`/api/part/${part_id}/bom-validate/`, {
|
||||
method: 'PUT',
|
||||
fields: {
|
||||
valid: {},
|
||||
},
|
||||
preFormContent: html,
|
||||
title: '{% trans "Validate Bill of Materials" %}',
|
||||
reload: options.reload,
|
||||
onSuccess: function(response) {
|
||||
showMessage('{% trans "Validated Bill of Materials" %}');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* Duplicate a BOM */
|
||||
function duplicateBom(part_id, options={}) {
|
||||
|
||||
constructForm(`/api/part/${part_id}/bom-copy/`, {
|
||||
method: 'POST',
|
||||
fields: {
|
||||
part: {
|
||||
icon: 'fa-shapes',
|
||||
filters: {
|
||||
assembly: true,
|
||||
exclude_tree: part_id,
|
||||
}
|
||||
},
|
||||
include_inherited: {},
|
||||
remove_existing: {},
|
||||
skip_invalid: {},
|
||||
},
|
||||
confirm: true,
|
||||
title: '{% trans "Copy Bill of Materials" %}',
|
||||
onSuccess: function(response) {
|
||||
if (options.success) {
|
||||
options.success(response);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function partStockLabel(part, options={}) {
|
||||
|
||||
if (part.in_stock) {
|
||||
@ -621,7 +676,9 @@ function loadPartParameterTable(table, url, options) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
|
||||
// setupFilterList("#part-parameters", $(table));
|
||||
var filterTarget = options.filterTarget || '#filter-list-parameters';
|
||||
|
||||
setupFilterList('part-parameters', $(table), filterTarget);
|
||||
|
||||
$(table).inventreeTable({
|
||||
url: url,
|
||||
@ -727,7 +784,7 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) {
|
||||
options.params.part_detail = true;
|
||||
options.params.order_detail = true;
|
||||
|
||||
var filters = loadTableFilters('partpurchaseorders');
|
||||
var filters = loadTableFilters('purchaseorderlineitem');
|
||||
|
||||
for (var key in options.params) {
|
||||
filters[key] = options.params[key];
|
||||
@ -871,7 +928,7 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) {
|
||||
if (row.received >= row.quantity) {
|
||||
// Already recevied
|
||||
return `<span class='badge bg-success rounded-pill'>{% trans "Received" %}</span>`;
|
||||
} else {
|
||||
} else if (row.order_detail && row.order_detail.status == {{ PurchaseOrderStatus.PLACED }}) {
|
||||
var html = `<div class='btn-group' role='group'>`;
|
||||
var pk = row.pk;
|
||||
|
||||
@ -879,6 +936,8 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) {
|
||||
|
||||
html += `</div>`;
|
||||
return html;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,7 @@
|
||||
loadStockTestResultsTable,
|
||||
loadStockTrackingTable,
|
||||
loadTableFilters,
|
||||
mergeStockItems,
|
||||
removeStockRow,
|
||||
serializeStockItem,
|
||||
stockItemFields,
|
||||
@ -595,17 +596,17 @@ function assignStockToCustomer(items, options={}) {
|
||||
buttons += '</div>';
|
||||
|
||||
html += `
|
||||
<tr id='stock_item_${pk}' class='stock-item'row'>
|
||||
<td id='part_${pk}'>${thumbnail} ${part.full_name}</td>
|
||||
<td id='stock_${pk}'>
|
||||
<div id='div_id_items_item_${pk}'>
|
||||
${quantity}
|
||||
<div id='errors-items_item_${pk}'></div>
|
||||
</div>
|
||||
</td>
|
||||
<td id='location_${pk}'>${location}</td>
|
||||
<td id='buttons_${pk}'>${buttons}</td>
|
||||
</tr>
|
||||
<tr id='stock_item_${pk}' class='stock-item-row'>
|
||||
<td id='part_${pk}'>${thumbnail} ${part.full_name}</td>
|
||||
<td id='stock_${pk}'>
|
||||
<div id='div_id_items_item_${pk}'>
|
||||
${quantity}
|
||||
<div id='errors-items_item_${pk}'></div>
|
||||
</div>
|
||||
</td>
|
||||
<td id='location_${pk}'>${location}</td>
|
||||
<td id='buttons_${pk}'>${buttons}</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -615,13 +616,13 @@ function assignStockToCustomer(items, options={}) {
|
||||
method: 'POST',
|
||||
preFormContent: html,
|
||||
fields: {
|
||||
'customer': {
|
||||
customer: {
|
||||
value: options.customer,
|
||||
filters: {
|
||||
is_customer: true,
|
||||
},
|
||||
},
|
||||
'notes': {},
|
||||
notes: {},
|
||||
},
|
||||
confirm: true,
|
||||
confirmMessage: '{% trans "Confirm stock assignment" %}',
|
||||
@ -694,6 +695,184 @@ function assignStockToCustomer(items, options={}) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Merge multiple stock items together
|
||||
*/
|
||||
function mergeStockItems(items, options={}) {
|
||||
|
||||
// Generate HTML content for the form
|
||||
var html = `
|
||||
<div class='alert alert-block alert-danger'>
|
||||
<h5>{% trans "Warning: Merge operation cannot be reversed" %}</h5>
|
||||
<strong>{% trans "Some information will be lost when merging stock items" %}:</strong>
|
||||
<ul>
|
||||
<li>{% trans "Stock transaction history will be deleted for merged items" %}</li>
|
||||
<li>{% trans "Supplier part information will be deleted for merged items" %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
|
||||
html += `
|
||||
<table class='table table-striped table-condensed' id='stock-merge-table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Part" %}</th>
|
||||
<th>{% trans "Stock Item" %}</th>
|
||||
<th>{% trans "Location" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
// Keep track of how many "locations" there are
|
||||
var locations = [];
|
||||
|
||||
for (var idx = 0; idx < items.length; idx++) {
|
||||
var item = items[idx];
|
||||
|
||||
var pk = item.pk;
|
||||
|
||||
if (item.location && !locations.includes(item.location)) {
|
||||
locations.push(item.location);
|
||||
}
|
||||
|
||||
var part = item.part_detail;
|
||||
var location = locationDetail(item, false);
|
||||
|
||||
var thumbnail = thumbnailImage(part.thumbnail || part.image);
|
||||
|
||||
var quantity = '';
|
||||
|
||||
if (item.serial && item.quantity == 1) {
|
||||
quantity = `{% trans "Serial" %}: ${item.serial}`;
|
||||
} else {
|
||||
quantity = `{% trans "Quantity" %}: ${item.quantity}`;
|
||||
}
|
||||
|
||||
quantity += stockStatusDisplay(item.status, {classes: 'float-right'});
|
||||
|
||||
var buttons = `<div class='btn-group' role='group'>`;
|
||||
|
||||
buttons += makeIconButton(
|
||||
'fa-times icon-red',
|
||||
'button-stock-item-remove',
|
||||
pk,
|
||||
'{% trans "Remove row" %}',
|
||||
);
|
||||
|
||||
html += `
|
||||
<tr id='stock_item_${pk}' class='stock-item-row'>
|
||||
<td id='part_${pk}'>${thumbnail} ${part.full_name}</td>
|
||||
<td id='stock_${pk}'>
|
||||
<div id='div_id_items_item_${pk}'>
|
||||
${quantity}
|
||||
<div id='errors-items_item_${pk}'></div>
|
||||
</div>
|
||||
</td>
|
||||
<td id='location_${pk}'>${location}</td>
|
||||
<td id='buttons_${pk}'>${buttons}</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
html += '</tbody></table>';
|
||||
|
||||
var location = locations.length == 1 ? locations[0] : null;
|
||||
|
||||
constructForm('{% url "api-stock-merge" %}', {
|
||||
method: 'POST',
|
||||
preFormContent: html,
|
||||
fields: {
|
||||
location: {
|
||||
value: location,
|
||||
icon: 'fa-sitemap',
|
||||
},
|
||||
notes: {},
|
||||
allow_mismatched_suppliers: {},
|
||||
allow_mismatched_status: {},
|
||||
},
|
||||
confirm: true,
|
||||
confirmMessage: '{% trans "Confirm stock item merge" %}',
|
||||
title: '{% trans "Merge Stock Items" %}',
|
||||
afterRender: function(fields, opts) {
|
||||
// Add button callbacks to remove rows
|
||||
$(opts.modal).find('.button-stock-item-remove').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
$(opts.modal).find(`#stock_item_${pk}`).remove();
|
||||
});
|
||||
},
|
||||
onSubmit: function(fields, opts) {
|
||||
|
||||
// Extract data elements from the form
|
||||
var data = {
|
||||
items: [],
|
||||
};
|
||||
|
||||
var item_pk_values = [];
|
||||
|
||||
items.forEach(function(item) {
|
||||
var pk = item.pk;
|
||||
|
||||
// Does the row still exist in the form?
|
||||
var row = $(opts.modal).find(`#stock_item_${pk}`);
|
||||
|
||||
if (row.exists()) {
|
||||
item_pk_values.push(pk);
|
||||
|
||||
data.items.push({
|
||||
item: pk,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var extra_fields = [
|
||||
'location',
|
||||
'notes',
|
||||
'allow_mismatched_suppliers',
|
||||
'allow_mismatched_status',
|
||||
];
|
||||
|
||||
extra_fields.forEach(function(field) {
|
||||
data[field] = getFormFieldValue(field, fields[field], opts);
|
||||
});
|
||||
|
||||
opts.nested = {
|
||||
'items': item_pk_values
|
||||
};
|
||||
|
||||
// Submit the form data
|
||||
inventreePut(
|
||||
'{% url "api-stock-merge" %}',
|
||||
data,
|
||||
{
|
||||
method: 'POST',
|
||||
success: function(response) {
|
||||
$(opts.modal).modal('hide');
|
||||
|
||||
if (options.success) {
|
||||
options.success(response);
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
switch (xhr.status) {
|
||||
case 400:
|
||||
handleFormErrors(xhr.responseJSON, fields, opts);
|
||||
break;
|
||||
default:
|
||||
$(opts.modal).modal('hide');
|
||||
showApiError(xhr, opts.url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform stock adjustments
|
||||
*/
|
||||
@ -1289,7 +1468,7 @@ function loadStockTable(table, options) {
|
||||
|
||||
var params = options.params || {};
|
||||
|
||||
var filterListElement = options.filterList || '#filter-list-stock';
|
||||
var filterTarget = options.filterTarget || '#filter-list-stock';
|
||||
|
||||
var filters = {};
|
||||
|
||||
@ -1305,7 +1484,7 @@ function loadStockTable(table, options) {
|
||||
original[k] = params[k];
|
||||
}
|
||||
|
||||
setupFilterList(filterKey, table, filterListElement);
|
||||
setupFilterList(filterKey, table, filterTarget);
|
||||
|
||||
// Override the default values, or add new ones
|
||||
for (var key in params) {
|
||||
@ -1458,7 +1637,7 @@ function loadStockTable(table, options) {
|
||||
}
|
||||
|
||||
if (row.quantity <= 0) {
|
||||
html += `<span class='badge rounded-pill bg-danger'>{% trans "Depleted" %}</span>`;
|
||||
html += `<span class='badge badge-right rounded-pill bg-danger'>{% trans "Depleted" %}</span>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
@ -1875,6 +2054,20 @@ function loadStockTable(table, options) {
|
||||
stockAdjustment('move');
|
||||
});
|
||||
|
||||
$('#multi-item-merge').click(function() {
|
||||
var items = $(table).bootstrapTable('getSelections');
|
||||
|
||||
mergeStockItems(items, {
|
||||
success: function(response) {
|
||||
$(table).bootstrapTable('refresh');
|
||||
|
||||
showMessage('{% trans "Merged stock items" %}', {
|
||||
style: 'success',
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#multi-item-assign').click(function() {
|
||||
|
||||
var items = $(table).bootstrapTable('getSelections');
|
||||
|
@ -381,6 +381,24 @@ function getAvailableTableFilters(tableKey) {
|
||||
};
|
||||
}
|
||||
|
||||
// Filters for "company" table
|
||||
if (tableKey == 'company') {
|
||||
return {
|
||||
is_manufacturer: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Manufacturer" %}',
|
||||
},
|
||||
is_supplier: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Supplier" %}',
|
||||
},
|
||||
is_customer: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Customer" %}',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Filters for the "Parts" table
|
||||
if (tableKey == 'parts') {
|
||||
return {
|
||||
|
@ -49,6 +49,7 @@
|
||||
<li><a class='dropdown-item' href="#" id='multi-item-remove' title='{% trans "Remove from selected stock items" %}'><span class='fas fa-minus-circle'></span> {% trans "Remove stock" %}</a></li>
|
||||
<li><a class='dropdown-item' href="#" id='multi-item-stocktake' title='{% trans "Stocktake selected stock items" %}'><span class='fas fa-check-circle'></span> {% trans "Count stock" %}</a></li>
|
||||
<li><a class='dropdown-item' href='#' id='multi-item-move' title='{% trans "Move selected stock items" %}'><span class='fas fa-exchange-alt'></span> {% trans "Move stock" %}</a></li>
|
||||
<li><a class='dropdown-item' href='#' id='multi-item-merge' title='{% trans "Merge selected stock items" %}'><span class='fas fa-object-group'></span> {% trans "Merge stock" %}</a></li>
|
||||
<li><a class='dropdown-item' href='#' id='multi-item-order' title='{% trans "Order selected items" %}'><span class='fas fa-shopping-cart'></span> {% trans "Order stock" %}</a></li>
|
||||
<li><a class='dropdown-item' href='#' id='multi-item-assign' title='{% trans "Assign to customer" %}'><span class='fas fa-user-tie'></span> {% trans "Assign to customer" %}</a></li>
|
||||
<li><a class='dropdown-item' href='#' id='multi-item-set-status' title='{% trans "Change status" %}'><span class='fas fa-exclamation-circle'></span> {% trans "Change stock status" %}</a></li>
|
||||
|
Reference in New Issue
Block a user