mirror of
https://github.com/inventree/InvenTree.git
synced 2025-08-03 02:21:34 +00:00
Merge branch 'master' into categories_parameters
This commit is contained in:
InvenTree
InvenTree
build
api.pytest_build.pytests.pyurls.pyviews.py
config_template.yamlfixtures
forms.pymigrations
models.pyserializers.pytemplates
build
locale
order
part
admin.pyapps.py
fixtures
forms.pymigrations
models.pytemplates
templatetags
test_bom_item.pytest_part.pytest_views.pyurls.pyviews.pystock
templates
InvenTree
bom_invalid.htmlbuild_pending.htmllatest_parts.htmllow_stock.htmlpo_outstanding.htmlrequired_stock_build.htmlsearching.htmlso_outstanding.htmlstarred_parts.html
js
modals.htmlstock_table.htmlusers
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block collapse_title %}
|
||||
<span class='fas fa-times-circle icon-header'></span>
|
||||
{% trans "BOM Waiting Validation" %}<span class='badge' id='bom-invalid-count'><span class='fas fa-spin fa-hourglass-half'></span></span>
|
||||
{% trans "BOM Waiting Validation" %}<span class='badge' id='bom-invalid-count'><span class='fas fa-spin fa-spinner'></span></span>
|
||||
{% endblock %}
|
||||
|
||||
{% block collapse_content %}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block collapse_title %}
|
||||
<span class='fas fa-cogs icon-header'></span>
|
||||
{% trans "Pending Builds" %}<span class='badge' id='build-pending-count'><span class='fas fa-spin fa-hourglass-half'></span></span>
|
||||
{% trans "Pending Builds" %}<span class='badge' id='build-pending-count'><span class='fas fa-spin fa-spinner'></span></span>
|
||||
{% endblock %}
|
||||
|
||||
{% block collapse_content %}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block collapse_title %}
|
||||
<span class='fas fa-newspaper icon-header'></span>
|
||||
{% trans "Latest Parts" %}<span class='badge' id='latest-parts-count'><span class='fas fa-spin fa-hourglass-half'></span></span>
|
||||
{% trans "Latest Parts" %}<span class='badge' id='latest-parts-count'><span class='fas fa-spin fa-spinner'></span></span>
|
||||
{% endblock %}
|
||||
|
||||
{% block collapse_content %}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block collapse_title %}
|
||||
<span class='fas fa-shopping-cart icon-header'></span>
|
||||
{% trans "Low Stock" %}<span class='badge' id='low-stock-count'><span class='fas fa-spin fa-hourglass-half'></span></span>
|
||||
{% trans "Low Stock" %}<span class='badge' id='low-stock-count'><span class='fas fa-spin fa-spinner'></span></span>
|
||||
{% endblock %}
|
||||
|
||||
{% block collapse_content %}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block collapse_title %}
|
||||
<span class='fas fa-sign-in-alt icon-header'></span>
|
||||
{% trans "Outstanding Purchase Orders" %}<span class='badge' id='po-outstanding-count'><span class='fas fa-spin fa-hourglass-half'></span></span>
|
||||
{% trans "Outstanding Purchase Orders" %}<span class='badge' id='po-outstanding-count'><span class='fas fa-spin fa-spinner'></span></span>
|
||||
{% endblock %}
|
||||
|
||||
{% block collapse_content %}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block collapse_title %}
|
||||
<span class='fas fa-bullhorn icon-header'></span>
|
||||
{% trans "Require Stock To Complete Build" %}<span class='badge' id='stock-to-build-count'><span class='fas fa-spin fa-hourglass-half'></span></span>
|
||||
{% trans "Require Stock To Complete Build" %}<span class='badge' id='stock-to-build-count'><span class='fas fa-spin fa-spinner'></span></span>
|
||||
{% endblock %}
|
||||
|
||||
{% block collapse_content %}
|
||||
|
@@ -1,3 +1,3 @@
|
||||
{% load i18n %}
|
||||
|
||||
<span class='fas fa-spin fa-hourglass-half'></span> <i>{% trans "Searching" %}</i>
|
||||
<span class='fas fa-spin fa-spinner'></span> <i>{% trans "Searching" %}</i>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block collapse_title %}
|
||||
<span class='fas fa-sign-out-alt icon-header'></span>
|
||||
{% trans "Outstanding Sales Orders" %}<span class='badge' id='so-outstanding-count'><span class='fas fa-spin fa-hourglass-half'></span></span>
|
||||
{% trans "Outstanding Sales Orders" %}<span class='badge' id='so-outstanding-count'><span class='fas fa-spin fa-spinner'></span></span>
|
||||
{% endblock %}
|
||||
|
||||
{% block collapse_content %}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block collapse_title %}
|
||||
<span class='fas fa-star icon-header'></span>
|
||||
{% trans "Starred Parts" %}<span class='badge' id='starred-parts-count'><span class='fas fa-spin fa-hourglass-half'></span></span>
|
||||
{% trans "Starred Parts" %}<span class='badge' id='starred-parts-count'><span class='fas fa-spin fa-spinner'></span></span>
|
||||
{% endblock %}
|
||||
|
||||
{% block collapse_content %}
|
||||
|
@@ -152,17 +152,7 @@ function loadBomTable(table, options) {
|
||||
|
||||
var sub_part = row.sub_part_detail;
|
||||
|
||||
if (sub_part.trackable) {
|
||||
html += makeIconBadge('fa-directions', '{% trans "Trackable part" %}');
|
||||
}
|
||||
|
||||
if (sub_part.virtual) {
|
||||
html += makeIconBadge('fa-ghost', '{% trans "Virtual part" %}');
|
||||
}
|
||||
|
||||
if (sub_part.is_template) {
|
||||
html += makeIconBadge('fa-clone', '{% trans "Template part" %}');
|
||||
}
|
||||
html += makePartIcons(row.sub_part_detail);
|
||||
|
||||
// Display an extra icon if this part is an assembly
|
||||
if (sub_part.assembly) {
|
||||
@@ -171,10 +161,6 @@ function loadBomTable(table, options) {
|
||||
html += renderLink(text, `/part/${row.sub_part}/bom/`);
|
||||
}
|
||||
|
||||
if (!sub_part.active) {
|
||||
html += `<span class='label label-warning label-right'>{% trans "Inactive" %}</span>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
@@ -307,8 +293,6 @@ function loadBomTable(table, options) {
|
||||
});
|
||||
}
|
||||
|
||||
// Configure the table (bootstrap-table)
|
||||
|
||||
// Function to request BOM data for sub-items
|
||||
// This function may be called recursively for multi-level BOMs
|
||||
function requestSubItems(bom_pk, part_pk) {
|
||||
@@ -538,4 +522,4 @@ function loadUsedInTable(table, options) {
|
||||
queryParams: filters,
|
||||
original: params,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,592 @@
|
||||
{% load i18n %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
function newBuildOrder(options={}) {
|
||||
/* Launch modal form to create a new BuildOrder.
|
||||
*/
|
||||
|
||||
launchModalForm(
|
||||
"{% url 'build-create' %}",
|
||||
{
|
||||
follow: true,
|
||||
data: options.data || {},
|
||||
callback: [
|
||||
{
|
||||
field: 'part',
|
||||
action: function(value) {
|
||||
inventreeGet(
|
||||
`/api/part/${value}/`, {},
|
||||
{
|
||||
success: function(response) {
|
||||
|
||||
//enableField('serial_numbers', response.trackable);
|
||||
//clearField('serial_numbers');
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function makeBuildOutputActionButtons(output, buildInfo) {
|
||||
/* Generate action buttons for a build output.
|
||||
*/
|
||||
|
||||
var buildId = buildInfo.pk;
|
||||
var outputId = output.pk;
|
||||
|
||||
var panel = `#allocation-panel-${outputId}`;
|
||||
|
||||
function reloadTable() {
|
||||
$(panel).find(`#allocation-table-${outputId}`).bootstrapTable('refresh');
|
||||
}
|
||||
|
||||
// Find the div where the buttons will be displayed
|
||||
var buildActions = $(panel).find(`#output-actions-${outputId}`);
|
||||
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
// Add a button to "auto allocate" against the build
|
||||
html += makeIconButton(
|
||||
'fa-magic icon-blue', 'button-output-auto', outputId,
|
||||
'{% trans "Auto-allocate stock items to this output" %}',
|
||||
);
|
||||
|
||||
// Add a button to "complete" the particular build output
|
||||
html += makeIconButton(
|
||||
'fa-check icon-green', 'button-output-complete', outputId,
|
||||
'{% trans "Complete build output" %}',
|
||||
{
|
||||
//disabled: true
|
||||
}
|
||||
);
|
||||
|
||||
// Add a button to "cancel" the particular build output (unallocate)
|
||||
html += makeIconButton(
|
||||
'fa-minus-circle icon-red', 'button-output-unallocate', outputId,
|
||||
'{% trans "Unallocate stock from build output" %}',
|
||||
);
|
||||
|
||||
// Add a button to "delete" the particular build output
|
||||
html += makeIconButton(
|
||||
'fa-trash-alt icon-red', 'button-output-delete', outputId,
|
||||
'{% trans "Delete build output" %}',
|
||||
);
|
||||
|
||||
// Add a button to "destroy" the particular build output (mark as damaged, scrap)
|
||||
// TODO
|
||||
|
||||
html += '</div>';
|
||||
|
||||
buildActions.html(html);
|
||||
|
||||
// Add callbacks for the buttons
|
||||
$(panel).find(`#button-output-auto-${outputId}`).click(function() {
|
||||
// Launch modal dialog to perform auto-allocation
|
||||
launchModalForm(`/build/${buildId}/auto-allocate/`,
|
||||
{
|
||||
data: {
|
||||
output: outputId,
|
||||
},
|
||||
success: reloadTable,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$(panel).find(`#button-output-complete-${outputId}`).click(function() {
|
||||
launchModalForm(
|
||||
`/build/${buildId}/complete-output/`,
|
||||
{
|
||||
data: {
|
||||
output: outputId,
|
||||
},
|
||||
reload: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$(panel).find(`#button-output-unallocate-${outputId}`).click(function() {
|
||||
launchModalForm(
|
||||
`/build/${buildId}/unallocate/`,
|
||||
{
|
||||
success: reloadTable,
|
||||
data: {
|
||||
output: outputId,
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$(panel).find(`#button-output-delete-${outputId}`).click(function() {
|
||||
launchModalForm(
|
||||
`/build/${buildId}/delete-output/`,
|
||||
{
|
||||
reload: true,
|
||||
data: {
|
||||
output: outputId
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
/*
|
||||
* Load the "allocation table" for a particular build output.
|
||||
*
|
||||
* Args:
|
||||
* - buildId: The PK of the Build object
|
||||
* - partId: The PK of the Part object
|
||||
* - output: The StockItem object which is the "output" of the build
|
||||
* - options:
|
||||
* -- table: The #id of the table (will be auto-calculated if not provided)
|
||||
*/
|
||||
|
||||
var buildId = buildInfo.pk;
|
||||
var partId = buildInfo.part;
|
||||
|
||||
var outputId = null;
|
||||
|
||||
outputId = output.pk;
|
||||
|
||||
var table = options.table;
|
||||
|
||||
if (options.table == null) {
|
||||
table = `#allocation-table-${outputId}`;
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
// Reload the entire build allocation table
|
||||
$(table).bootstrapTable('refresh');
|
||||
}
|
||||
|
||||
function requiredQuantity(row) {
|
||||
// Return the requied quantity for a given row
|
||||
|
||||
return row.quantity * output.quantity;
|
||||
}
|
||||
|
||||
function sumAllocations(row) {
|
||||
// Calculat total allocations for a given row
|
||||
if (!row.allocations) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var quantity = 0;
|
||||
|
||||
row.allocations.forEach(function(item) {
|
||||
quantity += item.quantity;
|
||||
});
|
||||
|
||||
return quantity;
|
||||
}
|
||||
|
||||
function setupCallbacks() {
|
||||
// Register button callbacks once table data are loaded
|
||||
|
||||
// Callback for 'allocate' button
|
||||
$(table).find(".button-add").click(function() {
|
||||
|
||||
// Primary key of the 'sub_part'
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
// Launch form to allocate new stock against this output
|
||||
launchModalForm("{% url 'build-item-create' %}", {
|
||||
success: reloadTable,
|
||||
data: {
|
||||
part: pk,
|
||||
build: buildId,
|
||||
install_into: outputId,
|
||||
},
|
||||
secondary: [
|
||||
{
|
||||
field: 'stock_item',
|
||||
label: '{% trans "New Stock Item" %}',
|
||||
title: '{% trans "Create new Stock Item" %}',
|
||||
url: '{% url "stock-item-create" %}',
|
||||
data: {
|
||||
part: pk,
|
||||
},
|
||||
},
|
||||
],
|
||||
callback: [
|
||||
{
|
||||
field: 'stock_item',
|
||||
action: function(value) {
|
||||
inventreeGet(
|
||||
`/api/stock/${value}/`, {},
|
||||
{
|
||||
success: function(response) {
|
||||
|
||||
// How many items are actually available for the given stock item?
|
||||
var available = response.quantity - response.allocated;
|
||||
|
||||
var field = getFieldByName('#modal-form', 'quantity');
|
||||
|
||||
// Allocation quantity initial value
|
||||
var initial = field.attr('value');
|
||||
|
||||
if (available < initial) {
|
||||
field.val(available);
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// Callback for 'build' button
|
||||
$(table).find('.button-build').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
// Extract row data from the table
|
||||
var idx = $(this).closest('tr').attr('data-index');
|
||||
var row = $(table).bootstrapTable('getData')[idx];
|
||||
|
||||
// Launch form to create a new build order
|
||||
launchModalForm('{% url "build-create" %}', {
|
||||
follow: true,
|
||||
data: {
|
||||
part: pk,
|
||||
parent: buildId,
|
||||
quantity: requiredQuantity(row) - sumAllocations(row),
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Callback for 'unallocate' button
|
||||
$(table).find('.button-unallocate').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
launchModalForm(`/build/${buildId}/unallocate/`,
|
||||
{
|
||||
success: reloadTable,
|
||||
data: {
|
||||
output: outputId,
|
||||
part: pk,
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Load table of BOM items
|
||||
$(table).inventreeTable({
|
||||
url: "{% url 'api-bom-list' %}",
|
||||
queryParams: {
|
||||
part: partId,
|
||||
sub_part_detail: true,
|
||||
},
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No BOM items found" %}';
|
||||
},
|
||||
name: 'build-allocation',
|
||||
uniqueId: 'sub_part',
|
||||
onPostBody: setupCallbacks,
|
||||
onLoadSuccess: function(tableData) {
|
||||
// Once the BOM data are loaded, request allocation data for this build output
|
||||
|
||||
inventreeGet('/api/build/item/',
|
||||
{
|
||||
build: buildId,
|
||||
output: outputId,
|
||||
},
|
||||
{
|
||||
success: function(data) {
|
||||
// Iterate through the returned data, and group by the part they point to
|
||||
var allocations = {};
|
||||
|
||||
// Total number of line items
|
||||
var totalLines = tableData.length;
|
||||
|
||||
// Total number of "completely allocated" lines
|
||||
var allocatedLines = 0;
|
||||
|
||||
data.forEach(function(item) {
|
||||
|
||||
// Group BuildItem objects by part
|
||||
var part = item.part;
|
||||
var key = parseInt(part);
|
||||
|
||||
if (!(key in allocations)) {
|
||||
allocations[key] = new Array();
|
||||
}
|
||||
|
||||
allocations[key].push(item);
|
||||
});
|
||||
|
||||
// Now update the allocations for each row in the table
|
||||
for (var key in allocations) {
|
||||
|
||||
// Select the associated row in the table
|
||||
var tableRow = $(table).bootstrapTable('getRowByUniqueId', key);
|
||||
|
||||
if (!tableRow) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set the allocation list for that row
|
||||
tableRow.allocations = allocations[key];
|
||||
|
||||
// Calculate the total allocated quantity
|
||||
var allocatedQuantity = sumAllocations(tableRow);
|
||||
|
||||
// Is this line item fully allocated?
|
||||
if (allocatedQuantity >= (tableRow.quantity * output.quantity)) {
|
||||
allocatedLines += 1;
|
||||
}
|
||||
|
||||
// Push the updated row back into the main table
|
||||
$(table).bootstrapTable('updateByUniqueId', key, tableRow, true);
|
||||
}
|
||||
|
||||
// Update the total progress for this build output
|
||||
var buildProgress = $(`#allocation-panel-${outputId}`).find($(`#output-progress-${outputId}`));
|
||||
|
||||
var progress = makeProgressBar(
|
||||
allocatedLines,
|
||||
totalLines
|
||||
);
|
||||
|
||||
buildProgress.html(progress);
|
||||
|
||||
// Update the available actions for this build output
|
||||
|
||||
makeBuildOutputActionButtons(output, buildInfo);
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
sortable: true,
|
||||
showColumns: false,
|
||||
detailViewByClick: true,
|
||||
detailView: true,
|
||||
detailFilter: function(index, row) {
|
||||
return row.allocations != null;
|
||||
},
|
||||
detailFormatter: function(index, row, element) {
|
||||
// Contruct an 'inner table' which shows which stock items have been allocated
|
||||
|
||||
var subTableId = `allocation-table-${row.pk}`;
|
||||
|
||||
var html = `<div class='sub-table'><table class='table table-condensed table-striped' id='${subTableId}'></table></div>`;
|
||||
|
||||
element.html(html);
|
||||
|
||||
var lineItem = row;
|
||||
|
||||
var subTable = $(`#${subTableId}`);
|
||||
|
||||
subTable.bootstrapTable({
|
||||
data: row.allocations,
|
||||
showHeader: true,
|
||||
columns: [
|
||||
{
|
||||
width: '50%',
|
||||
field: 'quantity',
|
||||
title: '{% trans "Assigned Stock" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
var text = '';
|
||||
|
||||
var url = '';
|
||||
|
||||
if (row.serial && row.quantity == 1) {
|
||||
text = `{% trans "Serial Number" %}: ${row.serial}`;
|
||||
} else {
|
||||
text = `{% trans "Quantity" %}: ${row.quantity}`;
|
||||
}
|
||||
|
||||
{% if build.status == BuildStatus.COMPLETE %}
|
||||
url = `/stock/item/${row.pk}/`;
|
||||
{% else %}
|
||||
url = `/stock/item/${row.stock_item}/`;
|
||||
{% endif %}
|
||||
|
||||
return renderLink(text, url);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'location',
|
||||
title: '{% trans "Location" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
if (row.stock_item_detail.location) {
|
||||
var text = row.stock_item_detail.location_name;
|
||||
var url = `/stock/location/${row.stock_item_detail.location}/`;
|
||||
|
||||
return renderLink(text, url);
|
||||
} else {
|
||||
return '<i>{% trans "No location set" %}</i>';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'actions',
|
||||
formatter: function(value, row, index, field) {
|
||||
/* Actions available for a particular stock item allocation:
|
||||
*
|
||||
* - Edit the allocation quantity
|
||||
* - Delete the allocation
|
||||
*/
|
||||
|
||||
var pk = row.pk;
|
||||
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
html += makeIconButton('fa-edit icon-blue', 'button-allocation-edit', pk, '{% trans "Edit stock allocation" %}');
|
||||
|
||||
html += makeIconButton('fa-trash-alt icon-red', 'button-allocation-delete', pk, '{% trans "Delete stock allocation" %}');
|
||||
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Assign button callbacks to the newly created allocation buttons
|
||||
subTable.find('.button-allocation-edit').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
launchModalForm(`/build/item/${pk}/edit/`, {
|
||||
success: reloadTable,
|
||||
});
|
||||
});
|
||||
|
||||
subTable.find('.button-allocation-delete').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
launchModalForm(`/build/item/${pk}/delete/`, {
|
||||
success: reloadTable,
|
||||
});
|
||||
});
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
field: 'pk',
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
field: 'sub_part_detail.full_name',
|
||||
title: '{% trans "Required 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;
|
||||
|
||||
var html = imageHoverIcon(thumb) + renderLink(name, url);
|
||||
|
||||
html += makePartIcons(row.sub_part_detail);
|
||||
|
||||
return html;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'reference',
|
||||
title: '{% trans "Reference" %}',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
field: 'quantity',
|
||||
title: '{% trans "Quantity Per" %}',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
field: 'sub_part_detail.stock',
|
||||
title: '{% trans "Available" %}',
|
||||
},
|
||||
{
|
||||
field: 'allocated',
|
||||
title: '{% trans "Allocated" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
var allocated = 0;
|
||||
|
||||
if (row.allocations) {
|
||||
row.allocations.forEach(function(item) {
|
||||
allocated += item.quantity;
|
||||
});
|
||||
}
|
||||
|
||||
var required = requiredQuantity(row);
|
||||
|
||||
return makeProgressBar(allocated, required);
|
||||
},
|
||||
sorter: function(valA, valB, rowA, rowB) {
|
||||
var aA = sumAllocations(rowA);
|
||||
var aB = sumAllocations(rowB);
|
||||
|
||||
var qA = rowA.quantity;
|
||||
var qB = rowB.quantity;
|
||||
|
||||
qA *= output.quantity;
|
||||
qB *= output.quantity;
|
||||
|
||||
// Handle the case where both numerators are zero
|
||||
if ((aA == 0) && (aB == 0)) {
|
||||
return (qA > qB) ? 1 : -1;
|
||||
}
|
||||
|
||||
// Handle the case where either denominator is zero
|
||||
if ((qA == 0) || (qB == 0)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
var progressA = parseFloat(aA) / qA;
|
||||
var progressB = parseFloat(aB) / qB;
|
||||
|
||||
// Handle the case where both ratios are equal
|
||||
if (progressA == progressB) {
|
||||
return (qA < qB) ? 1 : -1;
|
||||
}
|
||||
|
||||
return (progressA < progressB) ? 1 : -1;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'actions',
|
||||
title: '{% trans "Actions" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
// Generate action buttons for this build output
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
if (sumAllocations(row) < requiredQuantity(row)) {
|
||||
if (row.sub_part_detail.assembly) {
|
||||
html += makeIconButton('fa-tools icon-blue', 'button-build', row.sub_part, '{% trans "Build stock" %}');
|
||||
}
|
||||
|
||||
if (row.sub_part_detail.purchaseable) {
|
||||
html += makeIconButton('fa-shopping-cart icon-blue', 'button-buy', row.sub_part, '{% trans "Order stock" %}', {disabled: true});
|
||||
}
|
||||
|
||||
html += makeIconButton('fa-sign-in-alt icon-green', 'button-add', row.sub_part, '{% trans "Allocate stock" %}');
|
||||
}
|
||||
|
||||
html += makeIconButton(
|
||||
'fa-minus-circle icon-red', 'button-unallocate', row.sub_part,
|
||||
'{% trans "Unallocate stock" %}',
|
||||
{
|
||||
disabled: row.allocations == null
|
||||
}
|
||||
);
|
||||
|
||||
html += '</div>';
|
||||
|
||||
return html;
|
||||
}
|
||||
},
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadBuildTable(table, options) {
|
||||
// Display a table of Build objects
|
||||
|
||||
@@ -9,7 +595,7 @@ function loadBuildTable(table, options) {
|
||||
var filters = {};
|
||||
|
||||
if (!options.disableFilters) {
|
||||
loadTableFilters("build");
|
||||
filters = loadTableFilters("build");
|
||||
}
|
||||
|
||||
for (var key in params) {
|
||||
@@ -21,7 +607,7 @@ function loadBuildTable(table, options) {
|
||||
$(table).inventreeTable({
|
||||
method: 'get',
|
||||
formatNoMatches: function() {
|
||||
return "{% trans "No builds matching query" %}";
|
||||
return '{% trans "No builds matching query" %}';
|
||||
},
|
||||
url: options.url,
|
||||
queryParams: filters,
|
||||
@@ -62,15 +648,27 @@ function loadBuildTable(table, options) {
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
|
||||
var name = row.part_detail.full_name;
|
||||
var html = imageHoverIcon(row.part_detail.thumbnail);
|
||||
|
||||
return imageHoverIcon(row.part_detail.thumbnail) + renderLink(name, '/part/' + row.part + '/');
|
||||
html += renderLink(row.part_detail.full_name, `/part/${row.part}/`);
|
||||
html += makePartIcons(row.part_detail);
|
||||
|
||||
return html;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'quantity',
|
||||
title: '{% trans "Quantity" %}',
|
||||
title: '{% trans "Completed" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
return makeProgressBar(
|
||||
row.completed,
|
||||
row.quantity,
|
||||
{
|
||||
//style: 'max',
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
|
@@ -61,6 +61,45 @@ function toggleStar(options) {
|
||||
}
|
||||
|
||||
|
||||
function makePartIcons(part, options={}) {
|
||||
/* Render a set of icons for the given part.
|
||||
*/
|
||||
|
||||
var html = '';
|
||||
|
||||
if (part.trackable) {
|
||||
html += makeIconBadge('fa-directions', '{% trans "Trackable part" %}');
|
||||
}
|
||||
|
||||
if (part.virtual) {
|
||||
html += makeIconBadge('fa-ghost', '{% trans "Virtual part" %}');
|
||||
}
|
||||
|
||||
if (part.is_template) {
|
||||
html += makeIconBadge('fa-clone', '{% trans "Template part" %}');
|
||||
}
|
||||
|
||||
if (part.assembly) {
|
||||
html += makeIconBadge('fa-tools', '{% trans "Assembled part" %}');
|
||||
}
|
||||
|
||||
if (part.starred) {
|
||||
html += makeIconBadge('fa-star', '{% trans "Starred part" %}');
|
||||
}
|
||||
|
||||
if (part.salable) {
|
||||
html += makeIconBadge('fa-dollar-sign', title='{% trans "Salable part" %}');
|
||||
}
|
||||
|
||||
if (!part.active) {
|
||||
html += `<span class='label label-warning label-right'>{% trans "Inactive" %}</span>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
|
||||
}
|
||||
|
||||
|
||||
function loadPartVariantTable(table, partId, options={}) {
|
||||
/* Load part variant table
|
||||
*/
|
||||
@@ -340,40 +379,8 @@ function loadPartTable(table, url, options={}) {
|
||||
|
||||
var display = imageHoverIcon(row.thumbnail) + renderLink(name, '/part/' + row.pk + '/');
|
||||
|
||||
if (row.trackable) {
|
||||
display += makeIconBadge('fa-directions', '{% trans "Trackable part" %}');
|
||||
}
|
||||
display += makePartIcons(row);
|
||||
|
||||
if (row.virtual) {
|
||||
display += makeIconBadge('fa-ghost', '{% trans "Virtual part" %}');
|
||||
}
|
||||
|
||||
|
||||
if (row.is_template) {
|
||||
display += makeIconBadge('fa-clone', '{% trans "Template part" %}');
|
||||
}
|
||||
|
||||
if (row.assembly) {
|
||||
display += makeIconBadge('fa-tools', '{% trans "Assembled part" %}');
|
||||
}
|
||||
|
||||
if (row.starred) {
|
||||
display += makeIconBadge('fa-star', '{% trans "Starred part" %}');
|
||||
}
|
||||
|
||||
if (row.salable) {
|
||||
display += makeIconBadge('fa-dollar-sign', title='{% trans "Salable part" %}');
|
||||
}
|
||||
|
||||
/*
|
||||
if (row.component) {
|
||||
display = display + `<span class='fas fa-cogs label-right' title='Component part'></span>`;
|
||||
}
|
||||
*/
|
||||
|
||||
if (!row.active) {
|
||||
display += `<span class='label label-warning label-right'>{% trans "Inactive" %}</span>`;
|
||||
}
|
||||
return display;
|
||||
}
|
||||
});
|
||||
|
@@ -257,6 +257,56 @@ function loadStockTable(table, options) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
|
||||
function locationDetail(row) {
|
||||
/*
|
||||
* Function to display a "location" of a StockItem.
|
||||
*
|
||||
* Complicating factors: A StockItem may not actually *be* in a location!
|
||||
* - Could be at a customer
|
||||
* - Could be installed in another stock item
|
||||
* - Could be assigned to a sales order
|
||||
* - Could be currently in production!
|
||||
*
|
||||
* So, instead of being naive, we'll check!
|
||||
*/
|
||||
|
||||
// Display text
|
||||
var text = '';
|
||||
|
||||
// URL (optional)
|
||||
var url = '';
|
||||
|
||||
if (row.is_building && row.build) {
|
||||
// StockItem is currently being built!
|
||||
text = "{% trans "In production" %}";
|
||||
url = `/build/${row.build}/`;
|
||||
} else if (row.belongs_to) {
|
||||
// StockItem is installed inside a different StockItem
|
||||
text = `{% trans "Installed in Stock Item" %} ${row.belongs_to}`;
|
||||
url = `/stock/item/${row.belongs_to}/installed/`;
|
||||
} else if (row.customer) {
|
||||
// StockItem has been assigned to a customer
|
||||
text = "{% trans "Shipped to customer" %}";
|
||||
url = `/company/${row.customer}/assigned-stock/`;
|
||||
} else if (row.sales_order) {
|
||||
// StockItem has been assigned to a sales order
|
||||
text = "{% trans "Assigned to Sales Order" %}";
|
||||
url = `/order/sales-order/${row.sales_order}/`;
|
||||
} else if (row.location) {
|
||||
text = row.location_detail.pathstring;
|
||||
url = `/stock/location/${row.location}/`;
|
||||
} else {
|
||||
text = "<i>{% trans "No stock location set" %}</i>";
|
||||
url = '';
|
||||
}
|
||||
|
||||
if (url) {
|
||||
return renderLink(text, url);
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
table.inventreeTable({
|
||||
method: 'get',
|
||||
formatNoMatches: function() {
|
||||
@@ -274,11 +324,16 @@ function loadStockTable(table, options) {
|
||||
|
||||
var row = data[0];
|
||||
|
||||
if (field == 'part_detail.name') {
|
||||
if (field == 'part_detail.full_name') {
|
||||
|
||||
var name = row.part_detail.full_name;
|
||||
var html = imageHoverIcon(row.part_detail.thumbnail);
|
||||
|
||||
return imageHoverIcon(row.part_detail.thumbnail) + name + ' <i>(' + data.length + ' items)</i>';
|
||||
html += row.part_detail.full_name;
|
||||
html += ` <i>(${data.length} items)</i>`;
|
||||
|
||||
html += makePartIcons(row.part_detail);
|
||||
|
||||
return html;
|
||||
}
|
||||
else if (field == 'part_detail.IPN') {
|
||||
return row.part_detail.IPN;
|
||||
@@ -353,28 +408,20 @@ function loadStockTable(table, options) {
|
||||
|
||||
data.forEach(function(item) {
|
||||
|
||||
var loc = null;
|
||||
var detail = locationDetail(item);
|
||||
|
||||
if (item.location_detail) {
|
||||
loc = item.location_detail.pathstring;
|
||||
} else {
|
||||
loc = "{% trans "Undefined location" %}";
|
||||
}
|
||||
|
||||
if (!locations.includes(loc)) {
|
||||
locations.push(loc);
|
||||
if (!locations.includes(detail)) {
|
||||
locations.push(detail);
|
||||
}
|
||||
});
|
||||
|
||||
if (locations.length > 1) {
|
||||
if (locations.length == 1) {
|
||||
// Single location, easy!
|
||||
return locations[0];
|
||||
} else if (locations.length > 1) {
|
||||
return "In " + locations.length + " locations";
|
||||
} else {
|
||||
// A single location!
|
||||
if (row.location_detail) {
|
||||
return renderLink(row.location_detail.pathstring, `/stock/location/${row.location}/`);
|
||||
} else {
|
||||
return "<i>{% trans "Undefined location" %}</i>";
|
||||
}
|
||||
return "<i>{% trans "Undefined location" %}</i>";
|
||||
}
|
||||
} else if (field == 'notes') {
|
||||
var notes = [];
|
||||
@@ -417,7 +464,7 @@ function loadStockTable(table, options) {
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'part_detail.name',
|
||||
field: 'part_detail.full_name',
|
||||
title: '{% trans "Part" %}',
|
||||
sortable: true,
|
||||
switchable: false,
|
||||
@@ -429,6 +476,8 @@ function loadStockTable(table, options) {
|
||||
|
||||
html = imageHoverIcon(thumb) + renderLink(name, url);
|
||||
|
||||
html += makePartIcons(row.part_detail);
|
||||
|
||||
return html;
|
||||
}
|
||||
},
|
||||
@@ -465,33 +514,35 @@ function loadStockTable(table, options) {
|
||||
|
||||
var html = renderLink(val, `/stock/item/${row.pk}/`);
|
||||
|
||||
if (row.allocated) {
|
||||
html += `<span class='fas fa-bookmark label-right' title='{% trans "Stock item has been allocated" %}'></span>`;
|
||||
if (row.is_building) {
|
||||
html += makeIconBadge('fa-tools', '{% trans "Stock item is in production" %}');
|
||||
}
|
||||
|
||||
if (row.sales_order) {
|
||||
// Stock item has been assigned to a sales order
|
||||
html += makeIconBadge('fa-truck', '{% trans "Stock item assigned to sales order" %}');
|
||||
} else if (row.customer) {
|
||||
// StockItem has been assigned to a customer
|
||||
html += makeIconBadge('fa-user', '{% trans "Stock item assigned to customer" %}');
|
||||
}
|
||||
|
||||
if (row.customer) {
|
||||
html += `<span class='fas fa-user-tie label-right' title='{% trans "Stock item has been assigned to customer" %}'></span>`;
|
||||
} else {
|
||||
if (row.build_order) {
|
||||
html += `<span class='fas fa-tools label-right' title='{% trans "Stock item was assigned to a build order" %}'></span>`;
|
||||
} else if (row.sales_order) {
|
||||
html += `<span class='fas fa-dollar-sign label-right' title='{% trans "Stock item was assigned to a sales order" %}'></span>`;
|
||||
}
|
||||
if (row.allocated) {
|
||||
html += makeIconBadge('fa-bookmark', '{% trans "Stock item has been allocated" %}');
|
||||
}
|
||||
|
||||
if (row.belongs_to) {
|
||||
html += `<span class='fas fa-box label-right' title='{% trans "Stock item has been installed in another item" %}'></span>`;
|
||||
html += makeIconBadge('fa-box', '{% trans "Stock item has been installed in another item" %}');
|
||||
}
|
||||
|
||||
// Special stock status codes
|
||||
|
||||
// 65 = "REJECTED"
|
||||
if (row.status == 65) {
|
||||
html += `<span class='fas fa-times-circle label-right' title='{% trans "Stock item has been rejected" %}'></span>`;
|
||||
html += makeIconButton('fa-times-circle icon-red', '{% trans "Stock item has been rejected" %}');
|
||||
}
|
||||
// 70 = "LOST"
|
||||
else if (row.status == 70) {
|
||||
html += `<span class='fas fa-question-circle label-right' title='{% trans "Stock item is lost" %}'></span>`;
|
||||
html += makeIconButton('fa-question-circle','{% trans "Stock item is lost" %}');
|
||||
}
|
||||
|
||||
if (row.quantity <= 0) {
|
||||
@@ -519,24 +570,7 @@ function loadStockTable(table, options) {
|
||||
title: '{% trans "Location" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
if (row.belongs_to) {
|
||||
var text = "{% trans 'Installed in Stock Item ' %}" + row.belongs_to;
|
||||
var url = `/stock/item/${row.belongs_to}/installed/`;
|
||||
|
||||
return renderLink(text, url);
|
||||
} else if (row.customer) {
|
||||
var text = "{% trans "Shipped to customer" %}";
|
||||
return renderLink(text, `/company/${row.customer}/assigned-stock/`);
|
||||
} else if (row.sales_order) {
|
||||
var text = `{% trans "Assigned to sales order" %}`;
|
||||
return renderLink(text, `/order/sales-order/${row.sales_order}/`);
|
||||
}
|
||||
else if (value) {
|
||||
return renderLink(value, `/stock/location/${row.location}/`);
|
||||
}
|
||||
else {
|
||||
return '<i>{% trans "No stock location set" %}</i>';
|
||||
}
|
||||
return locationDetail(row);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -769,6 +803,7 @@ function createNewStockItem(options) {
|
||||
field: 'part',
|
||||
action: function(value) {
|
||||
|
||||
// Reload options for supplier part
|
||||
reloadFieldOptions(
|
||||
'supplier_part',
|
||||
{
|
||||
@@ -782,6 +817,18 @@ function createNewStockItem(options) {
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Disable serial number field if the part is not trackable
|
||||
inventreeGet(
|
||||
`/api/part/${value}/`, {},
|
||||
{
|
||||
success: function(response) {
|
||||
|
||||
enableField('serial_numbers', response.trackable);
|
||||
clearField('serial_numbers');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
@@ -868,7 +915,7 @@ function loadInstalledInTable(table, options) {
|
||||
url: "{% url 'api-bom-list' %}",
|
||||
queryParams: {
|
||||
part: options.part,
|
||||
trackable: true,
|
||||
sub_part_trackable: true,
|
||||
sub_part_detail: true,
|
||||
},
|
||||
showColumns: false,
|
||||
|
@@ -65,16 +65,16 @@ function getAvailableTableFilters(tableKey) {
|
||||
title: '{% trans "Is Serialized" %}',
|
||||
},
|
||||
serial_gte: {
|
||||
title: "{% trans "Serial number GTE" %}",
|
||||
description: "{% trans "Serial number greater than or equal to" %}"
|
||||
title: '{% trans "Serial number GTE" %}',
|
||||
description: '{% trans "Serial number greater than or equal to" %}'
|
||||
},
|
||||
serial_lte: {
|
||||
title: "{% trans "Serial number LTE" %}",
|
||||
description: "{% trans "Serial number less than or equal to" %}",
|
||||
title: '{% trans "Serial number LTE" %}',
|
||||
description: '{% trans "Serial number less than or equal to" %}',
|
||||
},
|
||||
serial: {
|
||||
title: "{% trans "Serial number" %}",
|
||||
description: "{% trans "Serial number" %}"
|
||||
title: '{% trans "Serial number" %}',
|
||||
description: '{% trans "Serial number" %}'
|
||||
},
|
||||
batch: {
|
||||
title: '{% trans "Batch" %}',
|
||||
@@ -111,6 +111,11 @@ function getAvailableTableFilters(tableKey) {
|
||||
title: '{% trans "In Stock" %}',
|
||||
description: '{% trans "Show items which are in stock" %}',
|
||||
},
|
||||
is_building: {
|
||||
type: 'bool',
|
||||
title: '{% trans "In Production" %}',
|
||||
description: '{% trans "Show items which are in production" %}',
|
||||
},
|
||||
installed: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Installed" %}',
|
||||
@@ -126,16 +131,16 @@ function getAvailableTableFilters(tableKey) {
|
||||
title: '{% trans "Is Serialized" %}',
|
||||
},
|
||||
serial: {
|
||||
title: "{% trans "Serial number" %}",
|
||||
description: "{% trans "Serial number" %}"
|
||||
title: '{% trans "Serial number" %}',
|
||||
description: '{% trans "Serial number" %}'
|
||||
},
|
||||
serial_gte: {
|
||||
title: "{% trans "Serial number GTE" %}",
|
||||
description: "{% trans "Serial number greater than or equal to" %}"
|
||||
title: '{% trans "Serial number GTE" %}',
|
||||
description: '{% trans "Serial number greater than or equal to" %}'
|
||||
},
|
||||
serial_lte: {
|
||||
title: "{% trans "Serial number LTE" %}",
|
||||
description: "{% trans "Serial number less than or equal to" %}",
|
||||
title: '{% trans "Serial number LTE" %}',
|
||||
description: '{% trans "Serial number less than or equal to" %}',
|
||||
},
|
||||
status: {
|
||||
options: stockCodes,
|
||||
@@ -154,7 +159,7 @@ function getAvailableTableFilters(tableKey) {
|
||||
return {
|
||||
result: {
|
||||
type: 'bool',
|
||||
title: "{% trans 'Test result' %}",
|
||||
title: '{% trans "Test result" %}',
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -164,7 +169,7 @@ function getAvailableTableFilters(tableKey) {
|
||||
return {
|
||||
required: {
|
||||
type: 'bool',
|
||||
title: "{% trans "Required" %}",
|
||||
title: '{% trans "Required" %}',
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -176,9 +181,9 @@ function getAvailableTableFilters(tableKey) {
|
||||
title: '{% trans "Build status" %}',
|
||||
options: buildCodes,
|
||||
},
|
||||
pending: {
|
||||
active: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Pending" %}',
|
||||
title: '{% trans "Active" %}',
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -1,3 +1,5 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class='modal fade modal-fixed-footer modal-primary' tabindex='-1' role='dialog' id='modal-form'>
|
||||
<div class='modal-dialog'>
|
||||
<div class='modal-content'>
|
||||
@@ -5,13 +7,16 @@
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h3 id='modal-title'>Form Title Here</h3>
|
||||
<h3 id='modal-title'><i>Form Title Here</i></h3>
|
||||
</div>
|
||||
<div class='alert alert-block alert-danger' id='form-validation-warning' style='display: none;'>
|
||||
{% trans "Form errors exist" %}
|
||||
</div>
|
||||
<div class='modal-form-content'>
|
||||
</div>
|
||||
<div class='modal-footer'>
|
||||
<button type='button' class='btn btn-default' id='modal-form-close' data-dismiss='modal'>Close</button>
|
||||
<button type='button' class='btn btn-primary' id='modal-form-submit'>Submit</button>
|
||||
<button type='button' class='btn btn-default' id='modal-form-close' data-dismiss='modal'>{% trans "Close" %}</button>
|
||||
<button type='button' class='btn btn-primary' id='modal-form-submit'>{% trans "Submit" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,13 +29,16 @@
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h3 id='modal-title'>Form Title Here</h3>
|
||||
<h3 id='modal-title'><i>Form Title Here</i></h3>
|
||||
</div>
|
||||
<div class='alert alert-block alert-danger' id='form-validation-warning' style="display: none;">
|
||||
{% trans "Form errors exist" %}
|
||||
</div>
|
||||
<div class='modal-form-content'>
|
||||
</div>
|
||||
<div class='modal-footer'>
|
||||
<button type='button' class='btn btn-default' id='modal-form-close' data-dismiss='modal'>Close</button>
|
||||
<button type='button' class='btn btn-primary' id='modal-form-submit'>Submit</button>
|
||||
<button type='button' class='btn btn-default' id='modal-form-close' data-dismiss='modal'>{% trans "Close" %}</button>
|
||||
<button type='button' class='btn btn-primary' id='modal-form-submit'>{% trans "Submit" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -18,14 +18,14 @@
|
||||
<button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}<span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
{% if roles.stock.change %}
|
||||
<li><a href="#" id='multi-item-add' title='{% trans "Add to selected stock items" %}'>{% trans "Add stock" %}</a></li>
|
||||
<li><a href="#" id='multi-item-remove' title='{% trans "Remove from selected stock items" %}'>{% trans "Remove stock" %}</a></li>
|
||||
<li><a href="#" id='multi-item-stocktake' title='{% trans "Stocktake selected stock items" %}'>{% trans "Count stock" %}</a></li>
|
||||
<li><a href='#' id='multi-item-move' title='{% trans "Move selected stock items" %}'>{% trans "Move stock" %}</a></li>
|
||||
<li><a href='#' id='multi-item-order' title='{% trans "Order selected items" %}'>{% trans "Order stock" %}</a></li>
|
||||
<li><a href="#" id='multi-item-add' title='{% trans "Add to selected stock items" %}'><span class='fas fa-plus-circle'></span> {% trans "Add stock" %}</a></li>
|
||||
<li><a 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 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 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 href='#' id='multi-item-order' title='{% trans "Order selected items" %}'><span class='fas fa-shopping-cart'></span> {% trans "Order stock" %}</a></li>
|
||||
{% endif %}
|
||||
{% if roles.stock.delete %}
|
||||
<li><a href='#' id='multi-item-delete' title='{% trans "Delete selected items" %}'>{% trans "Delete Stock" %}</a></li>
|
||||
<li><a href='#' id='multi-item-delete' title='{% trans "Delete selected items" %}'><span class='fas fa-trash-alt'></span> {% trans "Delete Stock" %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -39,4 +39,4 @@
|
||||
</div>
|
||||
|
||||
<table class='table table-striped table-condensed' data-toolbar='#button-toolbar' id='stock-table'>
|
||||
</table>
|
||||
</table>
|
||||
|
Reference in New Issue
Block a user