mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 04:55:44 +00:00
Merge branch 'master' of https://github.com/inventree/InvenTree into part-import
This commit is contained in:
@ -13,40 +13,43 @@
|
||||
{% block settings %}
|
||||
|
||||
<table class='table table-striped table-condensed'>
|
||||
{% include "InvenTree/settings/header.html" %}
|
||||
<tbody>
|
||||
{% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-dollar-sign" %}
|
||||
{% include "InvenTree/settings/setting.html" with key="CUSTOM_EXCHANGE_RATES" icon="fa-edit" %}
|
||||
<tr>
|
||||
<th>{% trans "Base Currency" %}</th>
|
||||
<th>{{ base_currency }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan='2'>{% trans "Exchange Rates" %}</th>
|
||||
</tr>
|
||||
{% for rate in rates %}
|
||||
<tr>
|
||||
<td>{{ rate.currency }}</td>
|
||||
<td>{{ rate.value }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<th>
|
||||
{% trans "Last Update" %}
|
||||
</th>
|
||||
<td>
|
||||
{% if rates_updated %}
|
||||
{{ rates_updated }}
|
||||
{% else %}
|
||||
<i>{% trans "Never" %}</i>
|
||||
{% endif %}
|
||||
<form action='{% url "settings-currencies-refresh" %}' method='post'>
|
||||
<div id='refresh-rates-form'>
|
||||
{% csrf_token %}
|
||||
<button type='submit' id='update-rates' class='btn btn-default float-right'>{% trans "Update Now" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<h4>{% blocktrans with cur=default_currency %}Exchange Rates - Convert to {{cur}}{% endblocktrans %}</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form action="{% url 'settings-currencies' %}" method="post">
|
||||
<div id='exchange_rate_form'>
|
||||
{% csrf_token %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% crispy form %}
|
||||
{% if custom_rates is False %}
|
||||
<button type="submit" class='btn btn-primary'>{% trans "Refresh Exchange Rates" %}</button>
|
||||
{% else %}
|
||||
<button type="submit" class='btn btn-primary'>{% trans "Update Exchange Rates" %}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
{% if api_rates_success is False %}
|
||||
var alert_msg = {% blocktrans %}"Failed to refresh exchange rates" {% endblocktrans %};
|
||||
showAlertOrCache("alert-danger", alert_msg, null, 5000);
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -15,6 +15,9 @@
|
||||
<li {% if tab == 'global' %} class='active' {% endif %}>
|
||||
<a href='{% url "settings-global" %}'><span class='fas fa-globe'></span> {% trans "Global" %}</a>
|
||||
</li>
|
||||
<li {% if tab == 'currencies' %} class='active'{% endif %}>
|
||||
<a href="{% url 'settings-currencies' %}"><span class='fas fa-dollar-sign'></span> {% trans "Currencies" %}</a>
|
||||
</li>
|
||||
<li {% if tab == 'report' %} class='active' {% endif %}>
|
||||
<a href='{% url "settings-report" %}'><span class='fas fa-file-pdf'></span> {% trans "Report" %}</a>
|
||||
</li>
|
||||
@ -36,8 +39,5 @@
|
||||
<li {% if tab == 'so' %} class='active'{% endif %}>
|
||||
<a href="{% url 'settings-so' %}"><span class='fas fa-truck'></span> {% trans "Sales Orders" %}</a>
|
||||
</li>
|
||||
<li {% if tab == 'currencies' %} class='active'{% endif %}>
|
||||
<a href="{% url 'settings-currencies' %}"><span class='fas fa-dollar-sign'></span> {% trans "Currencies" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
@ -37,12 +37,12 @@
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/brands.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/solid.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap_3.3.7_css_bootstrap.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-table.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-table-group-by.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'bootstrap-table/bootstrap-table.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'bootstrap-table/extensions/group-by-v2/bootstrap-table-group-by.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'bootstrap-table/extensions/filter-control/bootstrap-table-filter-control.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/select2.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/select2-bootstrap.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-toggle.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-table-filter-control.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fullcalendar/main.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'script/jquery-ui/jquery-ui.min.css' %}">
|
||||
|
||||
@ -119,26 +119,28 @@
|
||||
|
||||
<script type="text/javascript" src="{% static 'script/bootstrap/bootstrap.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/bootstrap/bootstrap-treeview.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/bootstrap/bootstrap-table.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/bootstrap/bootstrap-table-en-US.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/bootstrap/bootstrap-table-group-by.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/bootstrap/bootstrap-toggle.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/bootstrap/bootstrap-table-filter-control.js' %}"></script>
|
||||
<!-- <script type='text/javascript' src="{% static 'script/bootstrap/filter-control-utils.js' %}"></script> -->
|
||||
<script type='text/javascript' src="{% static 'bootstrap-table/bootstrap-table.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'bootstrap-table/bootstrap-table-en-US.min.js' %}"></script>
|
||||
|
||||
<!-- jquery-treegrid -->
|
||||
<script type='text/javascript' src='{% static "treegrid/js/jquery.treegrid.js" %}'></script>
|
||||
<script type='text/javascript' src='{% static "treegrid/js/jquery.treegrid.bootstrap3.js" %}'></script>
|
||||
|
||||
<!-- boostrap-table-treegrid -->
|
||||
<!-- boostrap-table extensions -->
|
||||
<script type='text/javascript' src='{% static "bootstrap-table/extensions/group-by-v2/bootstrap-table-group-by.js" %}'></script>
|
||||
<script type='text/javascript' src='{% static "bootstrap-table/extensions/filter-control/bootstrap-table-filter-control.js" %}'></script>
|
||||
<script type='text/javascript' src='{% static "bootstrap-table/extensions/treegrid/bootstrap-table-treegrid.js" %}'></script>
|
||||
<script type='text/javascript' src='{% static "bootstrap-table/extensions/custom-view/bootstrap-table-custom-view.js" %}'></script>
|
||||
|
||||
<!-- 3rd party general js -->
|
||||
<script type="text/javascript" src="{% static 'fullcalendar/main.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'fullcalendar/locales-all.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'script/select2/select2.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/moment.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/chart.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/clipboard.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/randomColor.min.js' %}"></script>
|
||||
|
||||
<!-- general InvenTree -->
|
||||
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
|
||||
|
@ -243,6 +243,22 @@ function loadBomTable(table, options) {
|
||||
}
|
||||
});
|
||||
|
||||
cols.push(
|
||||
{
|
||||
field: 'purchase_price_range',
|
||||
title: '{% trans "Purchase Price Range" %}',
|
||||
searchable: false,
|
||||
sortable: true,
|
||||
});
|
||||
|
||||
cols.push(
|
||||
{
|
||||
field: 'purchase_price_avg',
|
||||
title: '{% trans "Purchase Price Average" %}',
|
||||
searchable: false,
|
||||
sortable: true,
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
// TODO - Re-introduce the pricing column at a later stage,
|
||||
@ -269,11 +285,18 @@ function loadBomTable(table, options) {
|
||||
title: '{% trans "Optional" %}',
|
||||
searchable: false,
|
||||
formatter: function(value) {
|
||||
if (value == '1') return '{% trans "true" %}';
|
||||
if (value == '0') return '{% trans "false" %}';
|
||||
return yesNoLabel(value);
|
||||
}
|
||||
});
|
||||
|
||||
cols.push({
|
||||
field: 'allow_variants',
|
||||
title: '{% trans "Allow Variants" %}',
|
||||
formatter: function(value) {
|
||||
return yesNoLabel(value);
|
||||
}
|
||||
})
|
||||
|
||||
cols.push({
|
||||
field: 'inherited',
|
||||
title: '{% trans "Inherited" %}',
|
||||
@ -281,7 +304,7 @@ function loadBomTable(table, options) {
|
||||
formatter: function(value, row, index, field) {
|
||||
// This BOM item *is* inheritable, but is defined for this BOM
|
||||
if (!row.inherited) {
|
||||
return "-";
|
||||
return yesNoLabel(false);
|
||||
} else if (row.part == options.parent_id) {
|
||||
return '{% trans "Inherited" %}';
|
||||
} else {
|
||||
|
@ -372,7 +372,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
data.forEach(function(item) {
|
||||
|
||||
// Group BuildItem objects by part
|
||||
var part = item.part;
|
||||
var part = item.bom_part || item.part;
|
||||
var key = parseInt(part);
|
||||
|
||||
if (!(key in allocations)) {
|
||||
@ -461,6 +461,16 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
data: row.allocations,
|
||||
showHeader: true,
|
||||
columns: [
|
||||
{
|
||||
field: 'part',
|
||||
title: '{% trans "Part" %}',
|
||||
formatter: function(value, row) {
|
||||
|
||||
var html = imageHoverIcon(row.part_thumb);
|
||||
html += renderLink(row.part_name, `/part/${value}/`);
|
||||
return html;
|
||||
}
|
||||
},
|
||||
{
|
||||
width: '50%',
|
||||
field: 'quantity',
|
||||
|
@ -978,6 +978,7 @@ function showModalImage(image_url) {
|
||||
// Set image content
|
||||
$('#modal-image').attr('src', image_url);
|
||||
|
||||
modal.hide();
|
||||
modal.show();
|
||||
|
||||
modal.animate({
|
||||
|
@ -5,6 +5,14 @@
|
||||
* Requires api.js to be loaded first
|
||||
*/
|
||||
|
||||
function yesNoLabel(value) {
|
||||
if (value) {
|
||||
return `<span class='label label-green'>{% trans "YES" %}</span>`;
|
||||
} else {
|
||||
return `<span class='label label-yellow'>{% trans "NO" %}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleStar(options) {
|
||||
/* Toggle the 'starred' status of a part.
|
||||
* Performs AJAX queries and updates the display on the button.
|
||||
@ -278,6 +286,64 @@ function loadParametricPartTable(table, options={}) {
|
||||
}
|
||||
|
||||
|
||||
function partGridTile(part) {
|
||||
// Generate a "grid tile" view for a particular part
|
||||
|
||||
// Rows for table view
|
||||
var rows = '';
|
||||
|
||||
if (part.IPN) {
|
||||
rows += `<tr><td><b>{% trans "IPN" %}</b></td><td>${part.IPN}</td></tr>`;
|
||||
}
|
||||
|
||||
var stock = `${part.in_stock}`;
|
||||
|
||||
if (!part.in_stock) {
|
||||
stock = `<span class='label label-red'>{% trans "No Stock" %}</label>`;
|
||||
}
|
||||
|
||||
rows += `<tr><td><b>{% trans "Stock" %}</b></td><td>${stock}</td></tr>`;
|
||||
|
||||
if (part.on_order) {
|
||||
rows += `<tr><td><b>{$ trans "On Order" %}</b></td><td>${part.on_order}</td></tr>`;
|
||||
}
|
||||
|
||||
if (part.building) {
|
||||
rows += `<tr><td><b>{% trans "Building" %}</b></td><td>${part.building}</td></tr>`;
|
||||
}
|
||||
|
||||
var html = `
|
||||
|
||||
<div class='col-sm-3 card'>
|
||||
<div class='panel panel-default panel-inventree'>
|
||||
<div class='panel-heading'>
|
||||
<a href='/part/${part.pk}/'>
|
||||
<b>${part.full_name}</b>
|
||||
</a>
|
||||
${makePartIcons(part)}
|
||||
<br>
|
||||
<i>${part.description}</i>
|
||||
</div>
|
||||
<div class='panel-content'>
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<img src='${part.thumbnail}' class='card-thumb' onclick='showModalImage("${part.image}")'>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
<table class='table table-striped table-condensed'>
|
||||
${rows}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
function loadPartTable(table, url, options={}) {
|
||||
/* Load part listing data into specified table.
|
||||
*
|
||||
@ -452,8 +518,30 @@ function loadPartTable(table, url, options={}) {
|
||||
formatNoMatches: function() { return '{% trans "No parts found" %}'; },
|
||||
columns: columns,
|
||||
showColumns: true,
|
||||
});
|
||||
showCustomView: false,
|
||||
showCustomViewButton: false,
|
||||
customView: function(data) {
|
||||
|
||||
var html = '';
|
||||
|
||||
html = `<div class='row'>`;
|
||||
|
||||
data.forEach(function(row, index) {
|
||||
|
||||
// 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 += partGridTile(row);
|
||||
});
|
||||
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
});
|
||||
|
||||
if (options.buttons) {
|
||||
linkButtonsToSelection($(table), options.buttons);
|
||||
}
|
||||
@ -582,16 +670,6 @@ function loadPartCategoryTable(table, options) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function yesNoLabel(value) {
|
||||
if (value) {
|
||||
return `<span class='label label-green'>{% trans "YES" %}</span>`;
|
||||
} else {
|
||||
return `<span class='label label-yellow'>{% trans "NO" %}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function loadPartTestTemplateTable(table, options) {
|
||||
/*
|
||||
* Load PartTestTemplate table.
|
||||
@ -688,3 +766,60 @@ function loadPartTestTemplateTable(table, options) {
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadStockPricingChart(context, data) {
|
||||
return new Chart(context, {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {legend: {position: 'bottom'}},
|
||||
scales: {
|
||||
y: {
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
grid: {display: false},
|
||||
title: {
|
||||
display: true,
|
||||
text: '{% trans "Single Price" %}'
|
||||
}
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
position: 'right',
|
||||
grid: {display: false},
|
||||
titel: {
|
||||
display: true,
|
||||
text: '{% trans "Quantity" %}',
|
||||
position: 'right'
|
||||
}
|
||||
},
|
||||
y2: {
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
grid: {display: false},
|
||||
title: {
|
||||
display: true,
|
||||
text: '{% trans "Single Price Difference" %}'
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadBomChart(context, data) {
|
||||
return new Chart(context, {
|
||||
type: 'doughnut',
|
||||
data: data,
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {legend: {position: 'bottom'},
|
||||
scales: {xAxes: [{beginAtZero: true, ticks: {autoSkip: false}}]}}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -32,17 +32,32 @@ function removeStockRow(e) {
|
||||
}
|
||||
|
||||
|
||||
function passFailBadge(result) {
|
||||
function passFailBadge(result, align='float-right') {
|
||||
|
||||
if (result) {
|
||||
return `<span class='label label-green float-right'>{% trans "PASS" %}</span>`;
|
||||
return `<span class='label label-green ${align}'>{% trans "PASS" %}</span>`;
|
||||
} else {
|
||||
return `<span class='label label-red float-right'>{% trans "FAIL" %}</span>`;
|
||||
return `<span class='label label-red ${align}'>{% trans "FAIL" %}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
function noResultBadge() {
|
||||
return `<span class='label label-blue float-right'>{% trans "NO RESULT" %}</span>`;
|
||||
function noResultBadge(align='float-right') {
|
||||
return `<span class='label label-blue ${align}'>{% trans "NO RESULT" %}</span>`;
|
||||
}
|
||||
|
||||
function formatDate(row) {
|
||||
// Function for formatting date field
|
||||
var html = row.date;
|
||||
|
||||
if (row.user_detail) {
|
||||
html += `<span class='badge'>${row.user_detail.username}</span>`;
|
||||
}
|
||||
|
||||
if (row.attachment) {
|
||||
html += `<a href='${row.attachment}'><span class='fas fa-file-alt label-right'></span></a>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function loadStockTestResultsTable(table, options) {
|
||||
@ -50,21 +65,6 @@ function loadStockTestResultsTable(table, options) {
|
||||
* Load StockItemTestResult table
|
||||
*/
|
||||
|
||||
function formatDate(row) {
|
||||
// Function for formatting date field
|
||||
var html = row.date;
|
||||
|
||||
if (row.user_detail) {
|
||||
html += `<span class='badge'>${row.user_detail.username}</span>`;
|
||||
}
|
||||
|
||||
if (row.attachment) {
|
||||
html += `<a href='${row.attachment}'><span class='fas fa-file-alt label-right'></span></a>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function makeButtons(row, grouped) {
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
@ -81,17 +81,30 @@ function loadStockTestResultsTable(table, options) {
|
||||
return html;
|
||||
}
|
||||
|
||||
// First, load all the test templates
|
||||
var parent_node = "parent node";
|
||||
|
||||
table.inventreeTable({
|
||||
url: "{% url 'api-part-test-template-list' %}",
|
||||
method: 'get',
|
||||
name: 'testresult',
|
||||
treeEnable: true,
|
||||
rootParentId: parent_node,
|
||||
parentIdField: 'parent',
|
||||
idField: 'pk',
|
||||
uniqueId: 'key',
|
||||
treeShowField: 'test_name',
|
||||
formatNoMatches: function() {
|
||||
return "{% trans 'No test results found' %}";
|
||||
return '{% trans "No test results found" %}';
|
||||
},
|
||||
queryParams: {
|
||||
part: options.part,
|
||||
},
|
||||
onPostBody: function() {
|
||||
table.treegrid({
|
||||
treeColumn: 0,
|
||||
});
|
||||
table.treegrid("collapseAll");
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
field: 'pk',
|
||||
@ -130,100 +143,85 @@ function loadStockTestResultsTable(table, options) {
|
||||
{
|
||||
field: 'date',
|
||||
title: '{% trans "Test Date" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row) {
|
||||
return formatDate(row);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'buttons',
|
||||
formatter: function(value, row) {
|
||||
return makeButtons(row, false);
|
||||
}
|
||||
},
|
||||
],
|
||||
groupBy: true,
|
||||
groupByField: 'test_name',
|
||||
groupByFormatter: function(field, id, data) {
|
||||
|
||||
// Extract the "latest" row (data are returned in date order from the server)
|
||||
var latest = data[data.length-1];
|
||||
|
||||
switch (field) {
|
||||
case 'test_name':
|
||||
return latest.test_name + ` <i>(${data.length})</i>` + passFailBadge(latest.result);
|
||||
case 'value':
|
||||
return latest.value;
|
||||
case 'notes':
|
||||
return latest.notes;
|
||||
case 'date':
|
||||
return formatDate(latest);
|
||||
case 'buttons':
|
||||
// Buttons are done differently for grouped rows
|
||||
return makeButtons(latest, true);
|
||||
default:
|
||||
return "---";
|
||||
}
|
||||
},
|
||||
],
|
||||
onLoadSuccess: function(tableData) {
|
||||
// Once the test template data are loaded, query for results
|
||||
|
||||
// Set "parent" for each existing row
|
||||
tableData.forEach(function(item, idx) {
|
||||
tableData[idx].parent = options.stock_item;
|
||||
});
|
||||
|
||||
// Once the test template data are loaded, query for test results
|
||||
inventreeGet(
|
||||
"{% url 'api-stock-test-result-list' %}",
|
||||
'{% url "api-stock-test-result-list" %}',
|
||||
{
|
||||
stock_item: options.stock_item,
|
||||
user_detail: true,
|
||||
attachment_detail: true,
|
||||
ordering: "-date",
|
||||
},
|
||||
{
|
||||
success: function(data) {
|
||||
// Iterate through the returned test data
|
||||
data.forEach(function(item, index) {
|
||||
|
||||
// Iterate through the returned test result data, and group by test
|
||||
data.forEach(function(item) {
|
||||
var match = false;
|
||||
var override = false;
|
||||
|
||||
var key = item.key;
|
||||
|
||||
// Try to associate this result with a test row
|
||||
// Attempt to associate this result with an existing test
|
||||
tableData.forEach(function(row, index) {
|
||||
|
||||
|
||||
// The result matches the test template row
|
||||
if (key == row.key) {
|
||||
|
||||
// Force the names to be the same!
|
||||
item.test_name = row.test_name;
|
||||
item.required = row.required;
|
||||
|
||||
match = true;
|
||||
|
||||
if (row.result == null) {
|
||||
// The original row has not recorded a result - override!
|
||||
item.parent = parent_node;
|
||||
tableData[index] = item;
|
||||
override = true;
|
||||
} else {
|
||||
item.parent = row.pk;
|
||||
}
|
||||
|
||||
match = true;
|
||||
}
|
||||
});
|
||||
|
||||
// No match could be found (this is a new test!)
|
||||
// No match could be found
|
||||
if (!match) {
|
||||
|
||||
item.test_name = item.test;
|
||||
item.parent = parent_node;
|
||||
}
|
||||
|
||||
if (!override) {
|
||||
tableData.push(item);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Finally, push the data back into the table!
|
||||
// Push data back into the table
|
||||
table.bootstrapTable("load", tableData);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function loadStockTable(table, options) {
|
||||
/* Load data into a stock table with adjustable options.
|
||||
@ -1306,33 +1304,6 @@ function createNewStockItem(options) {
|
||||
function loadInstalledInTable(table, options) {
|
||||
/*
|
||||
* Display a table showing the stock items which are installed in this stock item.
|
||||
* This is a multi-level tree table, where the "top level" items are Part objects,
|
||||
* and the children of each top-level item are the associated installed stock items.
|
||||
*
|
||||
* The process for retrieving data and displaying the table is as follows:
|
||||
*
|
||||
* A) Get BOM data for the stock item
|
||||
* - It is assumed that the stock item will be for an assembly
|
||||
* (otherwise why are we installing stuff anyway?)
|
||||
* - Request BOM items for stock_item.part (and only for trackable sub items)
|
||||
*
|
||||
* B) Add parts to table
|
||||
* - Create rows for each trackable sub-part in the table
|
||||
*
|
||||
* C) Gather installed stock item data
|
||||
* - Get the list of installed stock items via the API
|
||||
* - If the Part reference is already in the table, add the sub-item as a child
|
||||
* - If this is a stock item for a *new* part, request that part from the API,
|
||||
* and add that part as a new row, then add the stock item as a child of that part
|
||||
*
|
||||
* D) Enjoy!
|
||||
*
|
||||
*
|
||||
* And the options object contains the following things:
|
||||
*
|
||||
* - stock_item: The PK of the master stock_item object
|
||||
* - part: The PK of the Part reference of the stock_item object
|
||||
* - quantity: The quantity of the stock item
|
||||
*/
|
||||
|
||||
function updateCallbacks() {
|
||||
@ -1355,246 +1326,88 @@ function loadInstalledInTable(table, options) {
|
||||
});
|
||||
}
|
||||
|
||||
table.inventreeTable(
|
||||
{
|
||||
url: "{% url 'api-bom-list' %}",
|
||||
queryParams: {
|
||||
part: options.part,
|
||||
sub_part_trackable: true,
|
||||
sub_part_detail: true,
|
||||
},
|
||||
showColumns: false,
|
||||
name: 'installed-in',
|
||||
detailView: true,
|
||||
detailViewByClick: true,
|
||||
detailFilter: function(index, row) {
|
||||
return row.installed_count && row.installed_count > 0;
|
||||
},
|
||||
detailFormatter: function(index, row, element) {
|
||||
var subTableId = `installed-table-${row.sub_part}`;
|
||||
table.inventreeTable({
|
||||
url: "{% url 'api-stock-list' %}",
|
||||
queryParams: {
|
||||
installed_in: options.stock_item,
|
||||
part_detail: true,
|
||||
},
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No installed items" %}';
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
field: 'part',
|
||||
title: '{% trans "Part" %}',
|
||||
formatter: function(value, row) {
|
||||
var html = '';
|
||||
|
||||
var html = `<div class='sub-table'><table class='table table-condensed table-striped' id='${subTableId}'></table></div>`;
|
||||
html += imageHoverIcon(row.part_detail.thumbnail);
|
||||
html += renderLink(row.part_detail.full_name, `/stock/item/${row.pk}/`);
|
||||
|
||||
element.html(html);
|
||||
|
||||
var subTable = $(`#${subTableId}`);
|
||||
|
||||
// Display a "sub table" showing all the linked stock items
|
||||
subTable.bootstrapTable({
|
||||
data: row.installed_items,
|
||||
showHeader: true,
|
||||
columns: [
|
||||
{
|
||||
field: 'item',
|
||||
title: '{% trans "Stock Item" %}',
|
||||
formatter: function(value, subrow, index, field) {
|
||||
|
||||
var pk = subrow.pk;
|
||||
var html = '';
|
||||
|
||||
if (subrow.serial && subrow.quantity == 1) {
|
||||
html += `{% trans "Serial" %}: ${subrow.serial}`;
|
||||
} else {
|
||||
html += `{% trans "Quantity" %}: ${subrow.quantity}`;
|
||||
}
|
||||
|
||||
return renderLink(html, `/stock/item/${subrow.pk}/`);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '{% trans "Status" %}',
|
||||
formatter: function(value, subrow, index, field) {
|
||||
return stockStatusDisplay(value);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'batch',
|
||||
title: '{% trans "Batch" %}',
|
||||
},
|
||||
{
|
||||
field: 'actions',
|
||||
title: '',
|
||||
formatter: function(value, subrow, index) {
|
||||
|
||||
var pk = subrow.pk;
|
||||
var html = '';
|
||||
|
||||
// Add some buttons yo!
|
||||
html += `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
html += makeIconButton('fa-unlink', 'button-uninstall', pk, "{% trans 'Uninstall stock item' %}");
|
||||
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
],
|
||||
onPostBody: function() {
|
||||
// Setup button callbacks
|
||||
subTable.find('.button-uninstall').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
launchModalForm(
|
||||
"{% url 'stock-item-uninstall' %}",
|
||||
{
|
||||
data: {
|
||||
'items[]': [pk],
|
||||
},
|
||||
success: function() {
|
||||
// Refresh entire table!
|
||||
table.bootstrapTable('refresh');
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
checkbox: true,
|
||||
title: '{% trans "Select" %}',
|
||||
searchable: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'pk',
|
||||
title: 'ID',
|
||||
visible: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'part',
|
||||
title: '{% trans "Part" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
|
||||
var url = `/part/${row.sub_part}/`;
|
||||
var thumb = row.sub_part_detail.thumbnail;
|
||||
var name = row.sub_part_detail.full_name;
|
||||
|
||||
html = imageHoverIcon(thumb) + renderLink(name, url);
|
||||
|
||||
if (row.not_in_bom) {
|
||||
html = `<i>${html}</i>`
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'installed',
|
||||
title: '{% trans "Installed" %}',
|
||||
sortable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
// Construct a progress showing how many items have been installed
|
||||
|
||||
var installed = row.installed_count || 0;
|
||||
var required = row.quantity || 0;
|
||||
|
||||
required *= options.quantity;
|
||||
|
||||
var progress = makeProgressBar(installed, required, {
|
||||
id: row.sub_part.pk,
|
||||
});
|
||||
|
||||
return progress;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'actions',
|
||||
switchable: false,
|
||||
formatter: function(value, row) {
|
||||
var pk = row.sub_part;
|
||||
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
html += makeIconButton('fa-link', 'button-install', pk, '{% trans "Install item" %}');
|
||||
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
],
|
||||
onLoadSuccess: function() {
|
||||
// Grab a list of parts which are actually installed in this stock item
|
||||
},
|
||||
{
|
||||
field: 'quantity',
|
||||
title: '{% trans "Quantity" %}',
|
||||
formatter: function(value, row) {
|
||||
|
||||
inventreeGet(
|
||||
"{% url 'api-stock-list' %}",
|
||||
var html = '';
|
||||
|
||||
if (row.serial && row.quantity == 1) {
|
||||
html += `{% trans "Serial" %}: ${row.serial}`;
|
||||
} else {
|
||||
html += `${row.quantity}`;
|
||||
}
|
||||
|
||||
return renderLink(html, `/stock/item/${row.pk}/`);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '{% trans "Status" %}',
|
||||
formatter: function(value, row) {
|
||||
return stockStatusDisplay(value);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'batch',
|
||||
title: '{% trans "Batch" %}',
|
||||
},
|
||||
{
|
||||
field: 'buttons',
|
||||
title: '',
|
||||
switchable: false,
|
||||
formatter: function(value, row) {
|
||||
var pk = row.pk;
|
||||
var html = '';
|
||||
|
||||
html += `<div class='btn-group float-right' role='group'>`;
|
||||
html += makeIconButton('fa-unlink', 'button-uninstall', pk, '{% trans "Uninstall Stock Item" %}');
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
],
|
||||
onPostBody: function() {
|
||||
// Assign callbacks to the buttons
|
||||
table.find('.button-uninstall').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
launchModalForm(
|
||||
'{% url "stock-item-uninstall" %}',
|
||||
{
|
||||
installed_in: options.stock_item,
|
||||
part_detail: true,
|
||||
},
|
||||
{
|
||||
success: function(stock_items) {
|
||||
|
||||
var table_data = table.bootstrapTable('getData');
|
||||
|
||||
stock_items.forEach(function(item) {
|
||||
|
||||
var match = false;
|
||||
|
||||
for (var idx = 0; idx < table_data.length; idx++) {
|
||||
|
||||
var row = table_data[idx];
|
||||
|
||||
// Check each row in the table to see if this stock item matches
|
||||
table_data.forEach(function(row) {
|
||||
|
||||
// Match on "sub_part"
|
||||
if (row.sub_part == item.part) {
|
||||
|
||||
// First time?
|
||||
if (row.installed_count == null) {
|
||||
row.installed_count = 0;
|
||||
row.installed_items = [];
|
||||
}
|
||||
|
||||
row.installed_count += item.quantity;
|
||||
row.installed_items.push(item);
|
||||
|
||||
// Push the row back into the table
|
||||
table.bootstrapTable('updateRow', idx, row, true);
|
||||
|
||||
match = true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (match) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
// The stock item did *not* match any items in the BOM!
|
||||
// Add a new row to the table...
|
||||
|
||||
// Contruct a new "row" to add to the table
|
||||
var new_row = {
|
||||
sub_part: item.part,
|
||||
sub_part_detail: item.part_detail,
|
||||
not_in_bom: true,
|
||||
installed_count: item.quantity,
|
||||
installed_items: [item],
|
||||
};
|
||||
|
||||
table.bootstrapTable('append', [new_row]);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Update button callback links
|
||||
updateCallbacks();
|
||||
data: {
|
||||
'items[]': pk,
|
||||
},
|
||||
success: function() {
|
||||
table.bootstrapTable('refresh');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
updateCallbacks();
|
||||
},
|
||||
)
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
@ -49,6 +49,10 @@ function getAvailableTableFilters(tableKey) {
|
||||
inherited: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Inherited" %}',
|
||||
},
|
||||
allow_variants: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Allow Variant Stock" %}',
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -14,7 +14,6 @@
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap_3.3.7_css_bootstrap.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/select2.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-table.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/inventree.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/brands.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/solid.css' %}">
|
||||
|
@ -13,7 +13,6 @@
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap_3.3.7_css_bootstrap.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/select2.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-table.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/inventree.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/brands.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/solid.css' %}">
|
||||
|
@ -14,7 +14,6 @@
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap_3.3.7_css_bootstrap.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/select2.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-table.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/inventree.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/brands.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/solid.css' %}">
|
||||
|
@ -14,7 +14,6 @@
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap_3.3.7_css_bootstrap.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/select2.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-table.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/inventree.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/brands.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/solid.css' %}">
|
||||
|
@ -14,7 +14,6 @@
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap_3.3.7_css_bootstrap.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/select2.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-table.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/inventree.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/brands.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/solid.css' %}">
|
||||
|
@ -14,7 +14,6 @@
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap_3.3.7_css_bootstrap.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/select2.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-table.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/inventree.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/brands.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/solid.css' %}">
|
||||
|
Reference in New Issue
Block a user