mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-19 21:45:39 +00:00
Merge branch 'inventree:master' into fr-1421-sso
This commit is contained in:
@ -22,13 +22,39 @@
|
||||
<td>{% trans "InvenTree Version" %}</td>
|
||||
<td>
|
||||
<a href="https://github.com/inventree/InvenTree/releases">{% inventree_version %}</a>{% include "clip.html" %}
|
||||
{% inventree_is_development as dev %}
|
||||
{% if dev %}
|
||||
<span class='label label-blue float-right'>{% trans "Development Version" %}</span>
|
||||
{% else %}
|
||||
{% if up_to_date %}
|
||||
<span class='label label-green float-right'>{% trans "Up to Date" %}</span>
|
||||
{% else %}
|
||||
<span class='label label-red float-right'>{% trans "Update Available" %}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if dev %}
|
||||
{% inventree_commit_hash as hash %}
|
||||
{% if hash %}
|
||||
<tr>
|
||||
<td><span class='fas fa-code-branch'></span></td>
|
||||
<td>{% trans "Commit Hash" %}</td><td>{{ hash }}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% inventree_commit_date as commit_date %}
|
||||
{% if commit_date %}
|
||||
<tr>
|
||||
<td><span class='fas fa-calendar-alt'></span></td>
|
||||
<td>{% trans "Commit Date" %}</td><td>{{ commit_date }}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><span class='fas fa-book'></span></td>
|
||||
<td>{% trans "InvenTree Documentation" %}</td>
|
||||
<td><a href="{% inventree_docs_url %}">{% inventree_docs_url %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-code'></span></td>
|
||||
<td>{% trans "API Version" %}</td>
|
||||
@ -44,25 +70,6 @@
|
||||
<td>{% trans "Django Version" %}</td>
|
||||
<td><a href="https://www.djangoproject.com/">{% django_version %}</a>{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
{% inventree_commit_hash as hash %}
|
||||
{% if hash %}
|
||||
<tr>
|
||||
<td><span class='fas fa-code-branch'></span></td>
|
||||
<td>{% trans "Commit Hash" %}</td><td>{{ hash }}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% inventree_commit_date as commit_date %}
|
||||
{% if commit_date %}
|
||||
<tr>
|
||||
<td><span class='fas fa-calendar-alt'></span></td>
|
||||
<td>{% trans "Commit Date" %}</td><td>{{ commit_date }}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><span class='fas fa-book'></span></td>
|
||||
<td>{% trans "InvenTree Documentation" %}</td>
|
||||
<td><a href="{% inventree_docs_url %}">{% inventree_docs_url %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fab fa-github'></span></td>
|
||||
<td>{% trans "View Code on GitHub" %}</td>
|
||||
|
@ -286,6 +286,8 @@ function constructForm(url, options) {
|
||||
constructFormBody({}, options);
|
||||
}
|
||||
|
||||
options.fields = options.fields || {};
|
||||
|
||||
// Save the URL
|
||||
options.url = url;
|
||||
|
||||
@ -545,6 +547,11 @@ function constructFormBody(fields, options) {
|
||||
|
||||
initializeGroups(fields, options);
|
||||
|
||||
if (options.afterRender) {
|
||||
// Custom callback function after form rendering
|
||||
options.afterRender(fields, options);
|
||||
}
|
||||
|
||||
// Scroll to the top
|
||||
$(options.modal).find('.modal-form-content-wrapper').scrollTop(0);
|
||||
}
|
||||
@ -1542,7 +1549,9 @@ function constructField(name, parameters, options) {
|
||||
html += `<div id='div_${field_name}' class='${form_classes}'>`;
|
||||
|
||||
// Add a label
|
||||
html += constructLabel(name, parameters);
|
||||
if (!options.hideLabels) {
|
||||
html += constructLabel(name, parameters);
|
||||
}
|
||||
|
||||
html += `<div class='controls'>`;
|
||||
|
||||
@ -1589,7 +1598,7 @@ function constructField(name, parameters, options) {
|
||||
html += `</div>`; // input-group
|
||||
}
|
||||
|
||||
if (parameters.help_text) {
|
||||
if (parameters.help_text && !options.hideLabels) {
|
||||
html += constructHelpText(name, parameters, options);
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
makeProgressBar,
|
||||
renderLink,
|
||||
select2Thumbnail,
|
||||
thumbnailImage
|
||||
yesNoLabel,
|
||||
*/
|
||||
|
||||
@ -56,6 +57,26 @@ function imageHoverIcon(url) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders a simple thumbnail image
|
||||
* @param {String} url is the image URL
|
||||
* @returns html <img> tag
|
||||
*/
|
||||
function thumbnailImage(url) {
|
||||
|
||||
if (!url) {
|
||||
url = '/static/img/blank_img.png';
|
||||
}
|
||||
|
||||
// TODO: Support insertion of custom classes
|
||||
|
||||
var html = `<img class='hover-img-thumb' src='${url}'>`;
|
||||
|
||||
return html;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Render a select2 thumbnail image
|
||||
function select2Thumbnail(image) {
|
||||
if (!image) {
|
||||
|
@ -793,14 +793,25 @@ function attachSecondaries(modal, secondaries) {
|
||||
function insertActionButton(modal, options) {
|
||||
/* Insert a custom submission button */
|
||||
|
||||
var html = `
|
||||
<span style='float: right;'>
|
||||
<button name='${options.name}' type='submit' class='btn btn-default modal-form-button' value='${options.name}'>
|
||||
${options.title}
|
||||
</button>
|
||||
</span>`;
|
||||
var element = $(modal).find('#modal-footer-buttons');
|
||||
|
||||
$(modal).find('#modal-footer-buttons').append(html);
|
||||
// check if button already present
|
||||
var already_present = false;
|
||||
for (var child=element[0].firstElementChild; child; child=child.nextElementSibling) {
|
||||
if (item.firstElementChild.name == options.name) {
|
||||
already_present = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (already_present == false) {
|
||||
var html = `
|
||||
<span style='float: right;'>
|
||||
<button name='${options.name}' type='submit' class='btn btn-default modal-form-button' value='${options.name}'>
|
||||
${options.title}
|
||||
</button>
|
||||
</span>`;
|
||||
element.append(html);
|
||||
}
|
||||
}
|
||||
|
||||
function attachButtons(modal, buttons) {
|
||||
|
@ -20,6 +20,7 @@
|
||||
/* exported
|
||||
createSalesOrder,
|
||||
editPurchaseOrderLineItem,
|
||||
loadPurchaseOrderLineItemTable,
|
||||
loadPurchaseOrderTable,
|
||||
loadSalesOrderAllocationTable,
|
||||
loadSalesOrderTable,
|
||||
@ -144,7 +145,6 @@ function newSupplierPartFromOrderWizard(e) {
|
||||
|
||||
if (!part) {
|
||||
part = $(src).closest('button').attr('part');
|
||||
console.log('parent: ' + part);
|
||||
}
|
||||
|
||||
createSupplierPart({
|
||||
@ -367,6 +367,271 @@ function loadPurchaseOrderTable(table, options) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load a table displaying line items for a particular PurchasesOrder
|
||||
* @param {String} table - HTML ID tag e.g. '#table'
|
||||
* @param {Object} options - options which must provide:
|
||||
* - order (integer PK)
|
||||
* - supplier (integer PK)
|
||||
* - allow_edit (boolean)
|
||||
* - allow_receive (boolean)
|
||||
*/
|
||||
function loadPurchaseOrderLineItemTable(table, options={}) {
|
||||
|
||||
function setupCallbacks() {
|
||||
if (options.allow_edit) {
|
||||
$(table).find('.button-line-edit').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
constructForm(`/api/order/po-line/${pk}/`, {
|
||||
fields: {
|
||||
part: {
|
||||
filters: {
|
||||
part_detail: true,
|
||||
supplier_detail: true,
|
||||
supplier: options.supplier,
|
||||
}
|
||||
},
|
||||
quantity: {},
|
||||
reference: {},
|
||||
purchase_price: {},
|
||||
purchase_price_currency: {},
|
||||
destination: {},
|
||||
notes: {},
|
||||
},
|
||||
title: '{% trans "Edit Line Item" %}',
|
||||
onSuccess: function() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(table).find('.button-line-delete').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
constructForm(`/api/order/po-line/${pk}/`, {
|
||||
method: 'DELETE',
|
||||
title: '{% trans "Delete Line Item" %}',
|
||||
onSuccess: function() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (options.allow_receive) {
|
||||
$(table).find('.button-line-receive').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
launchModalForm(`/order/purchase-order/${options.order}/receive/`, {
|
||||
success: function() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
},
|
||||
data: {
|
||||
line: pk,
|
||||
},
|
||||
secondary: [
|
||||
{
|
||||
field: 'location',
|
||||
label: '{% trans "New Location" %}',
|
||||
title: '{% trans "Create new stock location" %}',
|
||||
url: '{% url "stock-location-create" %}',
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$(table).inventreeTable({
|
||||
onPostBody: setupCallbacks,
|
||||
name: 'purchaseorderlines',
|
||||
sidePagination: 'server',
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No line items found" %}';
|
||||
},
|
||||
queryParams: {
|
||||
order: options.order,
|
||||
part_detail: true
|
||||
},
|
||||
url: '{% url "api-po-line-list" %}',
|
||||
showFooter: true,
|
||||
columns: [
|
||||
{
|
||||
field: 'pk',
|
||||
title: 'ID',
|
||||
visible: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'part',
|
||||
sortable: true,
|
||||
sortName: 'part_name',
|
||||
title: '{% trans "Part" %}',
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
if (row.part) {
|
||||
return imageHoverIcon(row.part_detail.thumbnail) + renderLink(row.part_detail.full_name, `/part/${row.part_detail.pk}/`);
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
},
|
||||
footerFormatter: function() {
|
||||
return '{% trans "Total" %}';
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'part_detail.description',
|
||||
title: '{% trans "Description" %}',
|
||||
},
|
||||
{
|
||||
sortable: true,
|
||||
sortName: 'SKU',
|
||||
field: 'supplier_part_detail.SKU',
|
||||
title: '{% trans "SKU" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
if (value) {
|
||||
return renderLink(value, `/supplier-part/${row.part}/`);
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
sortable: true,
|
||||
sortName: 'MPN',
|
||||
field: 'supplier_part_detail.manufacturer_part_detail.MPN',
|
||||
title: '{% trans "MPN" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
if (row.supplier_part_detail && row.supplier_part_detail.manufacturer_part) {
|
||||
return renderLink(value, `/manufacturer-part/${row.supplier_part_detail.manufacturer_part}/`);
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
sortable: true,
|
||||
field: 'reference',
|
||||
title: '{% trans "Reference" %}',
|
||||
},
|
||||
{
|
||||
sortable: true,
|
||||
field: 'quantity',
|
||||
title: '{% trans "Quantity" %}',
|
||||
footerFormatter: function(data) {
|
||||
return data.map(function(row) {
|
||||
return +row['quantity'];
|
||||
}).reduce(function(sum, i) {
|
||||
return sum + i;
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
sortable: true,
|
||||
field: 'purchase_price',
|
||||
title: '{% trans "Unit Price" %}',
|
||||
formatter: function(value, row) {
|
||||
return row.purchase_price_string || row.purchase_price;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'total_price',
|
||||
sortable: true,
|
||||
field: 'total_price',
|
||||
title: '{% trans "Total price" %}',
|
||||
formatter: function(value, row) {
|
||||
var total = row.purchase_price * row.quantity;
|
||||
var formatter = new Intl.NumberFormat('en-US', {style: 'currency', currency: row.purchase_price_currency});
|
||||
return formatter.format(total);
|
||||
},
|
||||
footerFormatter: function(data) {
|
||||
var total = data.map(function(row) {
|
||||
return +row['purchase_price']*row['quantity'];
|
||||
}).reduce(function(sum, i) {
|
||||
return sum + i;
|
||||
}, 0);
|
||||
|
||||
var currency = (data.slice(-1)[0] && data.slice(-1)[0].purchase_price_currency) || 'USD';
|
||||
|
||||
var formatter = new Intl.NumberFormat(
|
||||
'en-US',
|
||||
{
|
||||
style: 'currency',
|
||||
currency: currency
|
||||
}
|
||||
);
|
||||
|
||||
return formatter.format(total);
|
||||
}
|
||||
},
|
||||
{
|
||||
sortable: false,
|
||||
field: 'received',
|
||||
switchable: false,
|
||||
title: '{% trans "Received" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
return makeProgressBar(row.received, row.quantity, {
|
||||
id: `order-line-progress-${row.pk}`,
|
||||
});
|
||||
},
|
||||
sorter: function(valA, valB, rowA, rowB) {
|
||||
|
||||
if (rowA.received == 0 && rowB.received == 0) {
|
||||
return (rowA.quantity > rowB.quantity) ? 1 : -1;
|
||||
}
|
||||
|
||||
var progressA = parseFloat(rowA.received) / rowA.quantity;
|
||||
var progressB = parseFloat(rowB.received) / rowB.quantity;
|
||||
|
||||
return (progressA < progressB) ? 1 : -1;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'destination',
|
||||
title: '{% trans "Destination" %}',
|
||||
formatter: function(value, row) {
|
||||
if (value) {
|
||||
return renderLink(row.destination_detail.pathstring, `/stock/location/${value}/`);
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'notes',
|
||||
title: '{% trans "Notes" %}',
|
||||
},
|
||||
{
|
||||
switchable: false,
|
||||
field: 'buttons',
|
||||
title: '',
|
||||
formatter: function(value, row, index, field) {
|
||||
var html = `<div class='btn-group'>`;
|
||||
|
||||
var pk = row.pk;
|
||||
|
||||
if (options.allow_edit) {
|
||||
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" %}');
|
||||
}
|
||||
|
||||
if (options.allow_receive && row.received < row.quantity) {
|
||||
html += makeIconButton('fa-clipboard-check', 'button-line-receive', pk, '{% trans "Receive line item" %}');
|
||||
}
|
||||
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
},
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function loadSalesOrderTable(table, options) {
|
||||
|
||||
options.params = options.params || {};
|
||||
|
@ -768,7 +768,7 @@ function partGridTile(part) {
|
||||
var html = `
|
||||
|
||||
<div class='col-sm-3 card'>
|
||||
<div class='panel panel-default panel-inventree'>
|
||||
<div class='panel panel-default panel-inventree product-card-panel'>
|
||||
<div class='panel-heading'>
|
||||
<a href='/part/${part.pk}/'>
|
||||
<b>${part.full_name}</b>
|
||||
@ -1007,7 +1007,7 @@ function loadPartTable(table, url, options={}) {
|
||||
|
||||
// Force a new row every 4 columns, to prevent visual issues
|
||||
if ((index > 0) && (index % 4 == 0) && (index < data.length)) {
|
||||
html += `</div><div class='row'>`;
|
||||
html += `</div><div class='row full-height'>`;
|
||||
}
|
||||
|
||||
html += partGridTile(row);
|
||||
@ -1252,7 +1252,43 @@ function loadPartTestTemplateTable(table, options) {
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
onPostBody: function() {
|
||||
|
||||
table.find('.button-test-edit').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
var url = `/api/part/test-template/${pk}/`;
|
||||
|
||||
constructForm(url, {
|
||||
fields: {
|
||||
test_name: {},
|
||||
description: {},
|
||||
required: {},
|
||||
requires_value: {},
|
||||
requires_attachment: {},
|
||||
},
|
||||
title: '{% trans "Edit Test Result Template" %}',
|
||||
onSuccess: function() {
|
||||
table.bootstrapTable('refresh');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
table.find('.button-test-delete').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
var url = `/api/part/test-template/${pk}/`;
|
||||
|
||||
constructForm(url, {
|
||||
method: 'DELETE',
|
||||
title: '{% trans "Delete Test Result Template" %}',
|
||||
onSuccess: function() {
|
||||
table.bootstrapTable('refresh');
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -71,11 +71,7 @@
|
||||
<a class='dropdown-toggle' data-toggle='dropdown' href="#">
|
||||
{% if user.is_staff %}
|
||||
{% if not system_healthy %}
|
||||
{% if not django_q_running %}
|
||||
<span class='fas fa-exclamation-triangle icon-red'></span>
|
||||
{% else %}
|
||||
<span class='fas fa-exclamation-triangle icon-orange'></span>
|
||||
{% endif %}
|
||||
{% elif not up_to_date %}
|
||||
<span class='fas fa-info-circle icon-green'></span>
|
||||
{% endif %}
|
||||
@ -96,11 +92,7 @@
|
||||
{% if system_healthy or not user.is_staff %}
|
||||
<span class='fas fa-server'></span>
|
||||
{% else %}
|
||||
{% if not django_q_running %}
|
||||
<span class='fas fa-server icon-red'></span>
|
||||
{% else %}
|
||||
<span class='fas fa-server icon-orange'></span>
|
||||
{% endif %}
|
||||
<span class='fas fa-server icon-red'></span>
|
||||
{% endif %}
|
||||
</span> {% trans "System Information" %}
|
||||
</a></li>
|
||||
|
@ -16,7 +16,7 @@
|
||||
</button>
|
||||
|
||||
{% if owner_control.value == "True" and user in owners or user.is_superuser or owner_control.value == "False" %}
|
||||
{% if not read_only and roles.stock.add %}
|
||||
{% if not read_only and not prevent_new_stock and roles.stock.add %}
|
||||
<button class="btn btn-success" id='item-create' title='{% trans "New Stock Item" %}'>
|
||||
<span class='fas fa-plus-circle'></span>
|
||||
</button>
|
||||
|
Reference in New Issue
Block a user