mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-20 05:46:34 +00:00
Merge branch 'inventree:master' into trans-improv
This commit is contained in:
@ -8,6 +8,26 @@
|
||||
*/
|
||||
|
||||
|
||||
function bomItemFields() {
|
||||
|
||||
return {
|
||||
part: {
|
||||
hidden: true,
|
||||
},
|
||||
sub_part: {
|
||||
},
|
||||
quantity: {},
|
||||
reference: {},
|
||||
overage: {},
|
||||
note: {},
|
||||
allow_variants: {},
|
||||
inherited: {},
|
||||
optional: {},
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
function reloadBomTable(table, options) {
|
||||
|
||||
table.bootstrapTable('refresh');
|
||||
@ -262,13 +282,13 @@ function loadBomTable(table, options) {
|
||||
cols.push(
|
||||
{
|
||||
field: 'price_range',
|
||||
title: '{% trans "Buy Price" %}',
|
||||
title: '{% trans "Supplier Cost" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
if (value) {
|
||||
return value;
|
||||
} else {
|
||||
return "<span class='warning-msg'>{% trans 'No pricing available' %}</span>";
|
||||
return "<span class='warning-msg'>{% trans 'No supplier pricing available' %}</span>";
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -528,14 +548,15 @@ function loadBomTable(table, options) {
|
||||
var pk = $(this).attr('pk');
|
||||
var url = `/part/bom/${pk}/edit/`;
|
||||
|
||||
launchModalForm(
|
||||
url,
|
||||
{
|
||||
success: function() {
|
||||
reloadBomTable(table);
|
||||
}
|
||||
var fields = bomItemFields();
|
||||
|
||||
constructForm(`/api/bom/${pk}/`, {
|
||||
fields: fields,
|
||||
title: '{% trans "Edit BOM Item" %}',
|
||||
onSuccess: function() {
|
||||
reloadBomTable(table);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
table.on('click', '.bom-validate-button', function() {
|
||||
|
@ -927,7 +927,7 @@ function loadBuildTable(table, options) {
|
||||
},
|
||||
{
|
||||
field: 'responsible',
|
||||
title: '{% trans "Resposible" %}',
|
||||
title: '{% trans "Responsible" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
if (value)
|
||||
|
@ -265,6 +265,8 @@ function setupFilterList(tableKey, table, target) {
|
||||
// One blank slate, please
|
||||
element.empty();
|
||||
|
||||
element.append(`<button id='reload-${tableKey}' title='{% trans "Reload data" %}' class='btn btn-default filter-tag'><span class='fas fa-redo-alt'></span></button>`);
|
||||
|
||||
element.append(`<button id='${add}' title='{% trans "Add new filter" %}' class='btn btn-default filter-tag'><span class='fas fa-filter'></span></button>`);
|
||||
|
||||
if (Object.keys(filters).length > 0) {
|
||||
@ -279,6 +281,11 @@ function setupFilterList(tableKey, table, target) {
|
||||
element.append(`<div title='${description}' class='filter-tag'>${title} = ${value}<span ${tag}='${key}' class='close'>x</span></div>`);
|
||||
}
|
||||
|
||||
// Callback for reloading the table
|
||||
element.find(`#reload-${tableKey}`).click(function() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
});
|
||||
|
||||
// Add a callback for adding a new filter
|
||||
element.find(`#${add}`).click(function clicked() {
|
||||
|
||||
|
@ -240,6 +240,7 @@ function constructDeleteForm(fields, options) {
|
||||
* - hidden: Set to true to hide the field
|
||||
* - icon: font-awesome icon to display before the field
|
||||
* - prefix: Custom HTML prefix to display before the field
|
||||
* - data: map of data to fill out field values with
|
||||
* - focus: Name of field to focus on when modal is displayed
|
||||
* - preventClose: Set to true to prevent form from closing on success
|
||||
* - onSuccess: callback function when form action is successful
|
||||
@ -263,6 +264,11 @@ function constructForm(url, options) {
|
||||
// Default HTTP method
|
||||
options.method = options.method || 'PATCH';
|
||||
|
||||
// Construct an "empty" data object if not provided
|
||||
if (!options.data) {
|
||||
options.data = {};
|
||||
}
|
||||
|
||||
// Request OPTIONS endpoint from the API
|
||||
getApiEndpointOptions(url, function(OPTIONS) {
|
||||
|
||||
@ -346,10 +352,19 @@ function constructFormBody(fields, options) {
|
||||
// otherwise *all* fields will be displayed
|
||||
var displayed_fields = options.fields || fields;
|
||||
|
||||
// Handle initial data overrides
|
||||
if (options.data) {
|
||||
for (const field in options.data) {
|
||||
|
||||
if (field in fields) {
|
||||
fields[field].value = options.data[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Provide each field object with its own name
|
||||
for(field in fields) {
|
||||
fields[field].name = field;
|
||||
|
||||
|
||||
// If any "instance_filters" are defined for the endpoint, copy them across (overwrite)
|
||||
if (fields[field].instance_filters) {
|
||||
@ -366,6 +381,10 @@ function constructFormBody(fields, options) {
|
||||
|
||||
// TODO: Refactor the following code with Object.assign (see above)
|
||||
|
||||
// "before" and "after" renders
|
||||
fields[field].before = field_options.before;
|
||||
fields[field].after = field_options.after;
|
||||
|
||||
// Secondary modal options
|
||||
fields[field].secondary = field_options.secondary;
|
||||
|
||||
@ -560,10 +579,15 @@ function submitFormData(fields, options) {
|
||||
var has_files = false;
|
||||
|
||||
// Extract values for each field
|
||||
options.field_names.forEach(function(name) {
|
||||
for (var idx = 0; idx < options.field_names.length; idx++) {
|
||||
|
||||
var name = options.field_names[idx];
|
||||
|
||||
var field = fields[name] || null;
|
||||
|
||||
// Ignore visual fields
|
||||
if (field && field.type == 'candy') continue;
|
||||
|
||||
if (field) {
|
||||
|
||||
var value = getFormFieldValue(name, field, options);
|
||||
@ -593,7 +617,7 @@ function submitFormData(fields, options) {
|
||||
} else {
|
||||
console.log(`WARNING: Could not find field matching '${name}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var upload_func = inventreePut;
|
||||
|
||||
@ -1279,6 +1303,11 @@ function renderModelData(name, model, data, parameters, options) {
|
||||
*/
|
||||
function constructField(name, parameters, options) {
|
||||
|
||||
// Shortcut for simple visual fields
|
||||
if (parameters.type == 'candy') {
|
||||
return constructCandyInput(name, parameters, options);
|
||||
}
|
||||
|
||||
var field_name = `id_${name}`;
|
||||
|
||||
// Hidden inputs are rendered without label / help text / etc
|
||||
@ -1292,7 +1321,14 @@ function constructField(name, parameters, options) {
|
||||
form_classes += ' has-error';
|
||||
}
|
||||
|
||||
var html = `<div id='div_${field_name}' class='${form_classes}'>`;
|
||||
var html = '';
|
||||
|
||||
// Optional content to render before the field
|
||||
if (parameters.before) {
|
||||
html += parameters.before;
|
||||
}
|
||||
|
||||
html += `<div id='div_${field_name}' class='${form_classes}'>`;
|
||||
|
||||
// Add a label
|
||||
html += constructLabel(name, parameters);
|
||||
@ -1352,6 +1388,10 @@ function constructField(name, parameters, options) {
|
||||
html += `</div>`; // controls
|
||||
html += `</div>`; // form-group
|
||||
|
||||
if (parameters.after) {
|
||||
html += parameters.after;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
@ -1430,6 +1470,9 @@ function constructInput(name, parameters, options) {
|
||||
case 'date':
|
||||
func = constructDateInput;
|
||||
break;
|
||||
case 'candy':
|
||||
func = constructCandyInput;
|
||||
break;
|
||||
default:
|
||||
// Unsupported field type!
|
||||
break;
|
||||
@ -1658,6 +1701,17 @@ function constructDateInput(name, parameters, options) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Construct a "candy" field input
|
||||
* No actual field data!
|
||||
*/
|
||||
function constructCandyInput(name, parameters, options) {
|
||||
|
||||
return parameters.html;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Construct a 'help text' div based on the field parameters
|
||||
*
|
||||
|
@ -13,91 +13,213 @@ function yesNoLabel(value) {
|
||||
}
|
||||
}
|
||||
|
||||
// Construct fieldset for part forms
|
||||
function partFields(options={}) {
|
||||
|
||||
var fields = {
|
||||
category: {},
|
||||
name: {},
|
||||
IPN: {},
|
||||
revision: {},
|
||||
description: {},
|
||||
variant_of: {},
|
||||
keywords: {
|
||||
icon: 'fa-key',
|
||||
},
|
||||
units: {},
|
||||
link: {
|
||||
icon: 'fa-link',
|
||||
},
|
||||
default_location: {},
|
||||
default_supplier: {},
|
||||
default_expiry: {
|
||||
icon: 'fa-calendar-alt',
|
||||
},
|
||||
minimum_stock: {
|
||||
icon: 'fa-boxes',
|
||||
},
|
||||
attributes: {
|
||||
type: 'candy',
|
||||
html: `<hr><h4><i>{% trans "Part Attributes" %}</i></h4><hr>`
|
||||
},
|
||||
component: {
|
||||
value: global_settings.PART_COMPONENT,
|
||||
},
|
||||
assembly: {
|
||||
value: global_settings.PART_ASSEMBLY,
|
||||
},
|
||||
is_template: {
|
||||
value: global_settings.PART_TEMPLATE,
|
||||
},
|
||||
trackable: {
|
||||
value: global_settings.PART_TRACKABLE,
|
||||
},
|
||||
purchaseable: {
|
||||
value: global_settings.PART_PURCHASEABLE,
|
||||
},
|
||||
salable: {
|
||||
value: global_settings.PART_SALABLE,
|
||||
},
|
||||
virtual: {
|
||||
value: global_settings.PART_VIRTUAL,
|
||||
},
|
||||
};
|
||||
|
||||
// If editing a part, we can set the "active" status
|
||||
if (options.edit) {
|
||||
fields.active = {};
|
||||
}
|
||||
|
||||
// Pop expiry field
|
||||
if (!global_settings.STOCK_ENABLE_EXPIRY) {
|
||||
delete fields["default_expiry"];
|
||||
}
|
||||
|
||||
// Additional fields when "creating" a new part
|
||||
if (options.create) {
|
||||
|
||||
// No supplier parts available yet
|
||||
delete fields["default_supplier"];
|
||||
|
||||
fields.create = {
|
||||
type: 'candy',
|
||||
html: `<hr><h4><i>{% trans "Part Creation Options" %}</i></h4><hr>`,
|
||||
};
|
||||
|
||||
if (global_settings.PART_CREATE_INITIAL) {
|
||||
fields.initial_stock = {
|
||||
type: 'decimal',
|
||||
label: '{% trans "Initial Stock Quantity" %}',
|
||||
help_text: '{% trans "Initialize part stock with specified quantity" %}',
|
||||
};
|
||||
}
|
||||
|
||||
fields.copy_category_parameters = {
|
||||
type: 'boolean',
|
||||
label: '{% trans "Copy Category Parameters" %}',
|
||||
help_text: '{% trans "Copy parameter templates from selected part category" %}',
|
||||
value: global_settings.PART_CATEGORY_PARAMETERS,
|
||||
};
|
||||
}
|
||||
|
||||
// Additional fields when "duplicating" a part
|
||||
if (options.duplicate) {
|
||||
|
||||
fields.duplicate = {
|
||||
type: 'candy',
|
||||
html: `<hr><h4><i>{% trans "Part Duplication Options" %}</i></h4><hr>`,
|
||||
};
|
||||
|
||||
fields.copy_from = {
|
||||
type: 'integer',
|
||||
hidden: true,
|
||||
value: options.duplicate,
|
||||
},
|
||||
|
||||
fields.copy_image = {
|
||||
type: 'boolean',
|
||||
label: '{% trans "Copy Image" %}',
|
||||
help_text: '{% trans "Copy image from original part" %}',
|
||||
value: true,
|
||||
},
|
||||
|
||||
fields.copy_bom = {
|
||||
type: 'boolean',
|
||||
label: '{% trans "Copy BOM" %}',
|
||||
help_text: '{% trans "Copy bill of materials from original part" %}',
|
||||
value: global_settings.PART_COPY_BOM,
|
||||
};
|
||||
|
||||
fields.copy_parameters = {
|
||||
type: 'boolean',
|
||||
label: '{% trans "Copy Parameters" %}',
|
||||
help_text: '{% trans "Copy parameter data from original part" %}',
|
||||
value: global_settings.PART_COPY_PARAMETERS,
|
||||
};
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
|
||||
function categoryFields() {
|
||||
return {
|
||||
parent: {
|
||||
help_text: '{% trans "Parent part category" %}',
|
||||
},
|
||||
name: {},
|
||||
description: {},
|
||||
default_location: {},
|
||||
default_keywords: {
|
||||
icon: 'fa-key',
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Edit a PartCategory via the API
|
||||
function editCategory(pk, options={}) {
|
||||
|
||||
var url = `/api/part/category/${pk}/`;
|
||||
|
||||
var fields = categoryFields();
|
||||
|
||||
constructForm(url, {
|
||||
fields: fields,
|
||||
title: '{% trans "Edit Part Category" %}',
|
||||
reload: true,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function editPart(pk, options={}) {
|
||||
|
||||
var url = `/api/part/${pk}/`;
|
||||
|
||||
var fields = {
|
||||
category: {
|
||||
/*
|
||||
secondary: {
|
||||
label: '{% trans "New Category" %}',
|
||||
title: '{% trans "Create New Part Category" %}',
|
||||
api_url: '{% url "api-part-category-list" %}',
|
||||
method: 'POST',
|
||||
fields: {
|
||||
name: {},
|
||||
description: {},
|
||||
parent: {
|
||||
secondary: {
|
||||
title: '{% trans "New Parent" %}',
|
||||
api_url: '{% url "api-part-category-list" %}',
|
||||
method: 'POST',
|
||||
fields: {
|
||||
name: {},
|
||||
description: {},
|
||||
parent: {},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
*/
|
||||
},
|
||||
name: {
|
||||
placeholder: 'part name',
|
||||
},
|
||||
IPN: {},
|
||||
description: {},
|
||||
revision: {},
|
||||
keywords: {
|
||||
icon: 'fa-key',
|
||||
},
|
||||
variant_of: {},
|
||||
link: {
|
||||
icon: 'fa-link',
|
||||
},
|
||||
default_location: {
|
||||
/*
|
||||
secondary: {
|
||||
label: '{% trans "New Location" %}',
|
||||
title: '{% trans "Create new stock location" %}',
|
||||
},
|
||||
*/
|
||||
},
|
||||
default_supplier: {
|
||||
filters: {
|
||||
part: pk,
|
||||
part_detail: true,
|
||||
manufacturer_detail: true,
|
||||
supplier_detail: true,
|
||||
},
|
||||
/*
|
||||
secondary: {
|
||||
label: '{% trans "New Supplier Part" %}',
|
||||
title: '{% trans "Create new supplier part" %}',
|
||||
}
|
||||
*/
|
||||
},
|
||||
units: {},
|
||||
minimum_stock: {},
|
||||
virtual: {},
|
||||
is_template: {},
|
||||
assembly: {},
|
||||
component: {},
|
||||
trackable: {},
|
||||
purchaseable: {},
|
||||
salable: {},
|
||||
active: {},
|
||||
};
|
||||
var fields = partFields({
|
||||
edit: true
|
||||
});
|
||||
|
||||
constructForm(url, {
|
||||
fields: fields,
|
||||
title: '{% trans "Edit Part" %}',
|
||||
reload: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Launch form to duplicate a part
|
||||
function duplicatePart(pk, options={}) {
|
||||
|
||||
// First we need all the part information
|
||||
inventreeGet(`/api/part/${pk}/`, {}, {
|
||||
|
||||
success: function(data) {
|
||||
|
||||
var fields = partFields({
|
||||
duplicate: pk,
|
||||
});
|
||||
|
||||
// If we are making a "variant" part
|
||||
if (options.variant) {
|
||||
|
||||
// Override the "variant_of" field
|
||||
data.variant_of = pk;
|
||||
}
|
||||
|
||||
constructForm('{% url "api-part-list" %}', {
|
||||
method: 'POST',
|
||||
fields: fields,
|
||||
title: '{% trans "Duplicate Part" %}',
|
||||
data: data,
|
||||
onSuccess: function(data) {
|
||||
// Follow the new part
|
||||
location.href = `/part/${data.pk}/`;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -187,7 +187,7 @@ $.fn.inventreeTable = function(options) {
|
||||
if (!options.disablePagination) {
|
||||
options.pagination = true;
|
||||
options.paginationVAlign = options.paginationVAlign || 'both';
|
||||
options.pageSize = inventreeLoad(varName, 25);
|
||||
options.pageSize = options.pageSize || inventreeLoad(varName, 25);
|
||||
options.pageList = [25, 50, 100, 250, 'all'];
|
||||
options.totalField = 'count';
|
||||
options.dataField = 'results';
|
||||
|
Reference in New Issue
Block a user