diff --git a/InvenTree/InvenTree/static/script/inventree/stock.js b/InvenTree/InvenTree/static/script/inventree/stock.js
index 4e4b61aa93..950b0260e4 100644
--- a/InvenTree/InvenTree/static/script/inventree/stock.js
+++ b/InvenTree/InvenTree/static/script/inventree/stock.js
@@ -2,19 +2,6 @@
* Requires api.js to be loaded first
*/
-function getStockList(filters={}, options={}) {
- return inventreeGet('/api/stock/', filters, options);
-}
-
-function getStockDetail(pk, options={}) {
- return inventreeGet('/api/stock/' + pk + '/', {}, options)
-}
-
-function getStockLocations(filters={}, options={}) {
- return inventreeGet('/api/stock/location/', filters, options)
-}
-
-
/* Functions for interacting with stock management forms
*/
@@ -29,367 +16,6 @@ function removeStockRow(e) {
$('#' + row).remove();
}
-
-function loadStockTable(table, options) {
- /* Load data into a stock table with adjustable options.
- * Fetches data (via AJAX) and loads into a bootstrap table.
- * Also links in default button callbacks.
- *
- * Options:
- * url - URL for the stock query
- * params - query params for augmenting stock data request
- * groupByField - Column for grouping stock items
- * buttons - Which buttons to link to stock selection callbacks
- * filterList -
element where filters are displayed
- * disableFilters: If true, disable custom filters
- */
-
- // List of user-params which override the default filters
-
- options.params['part_detail'] = true;
- options.params['location_detail'] = true;
-
- var params = options.params || {};
-
- var filterListElement = options.filterList || "#filter-list-stock";
-
- var filters = {};
-
- if (!options.disableFilters) {
- filters = loadTableFilters("stock");
- }
-
- var original = {};
-
- for (var key in params) {
- original[key] = params[key];
- }
-
- setupFilterList("stock", table, filterListElement);
-
- // Override the default values, or add new ones
- for (var key in params) {
- filters[key] = params[key];
- }
-
- table.inventreeTable({
- method: 'get',
- formatNoMatches: function() {
- return 'No stock items matching query';
- },
- url: options.url,
- queryParams: filters,
- customSort: customGroupSorter,
- groupBy: true,
- original: original,
- groupByField: options.groupByField || 'part',
- groupByFormatter: function(field, id, data) {
-
- var row = data[0];
-
- if (field == 'part_name') {
-
- var name = row.part_detail.full_name;
-
- return imageHoverIcon(row.part_detail.thumbnail) + name + ' (' + data.length + ' items)';
- }
- else if (field == 'part_description') {
- return row.part_detail.description;
- }
- else if (field == 'quantity') {
- var stock = 0;
- var items = 0;
-
- data.forEach(function(item) {
- stock += parseFloat(item.quantity);
- items += 1;
- });
-
- stock = +stock.toFixed(5);
-
- return stock + " (" + items + " items)";
- } else if (field == 'status') {
- var statii = [];
-
- data.forEach(function(item) {
- var status = String(item.status);
-
- if (!status || status == '') {
- status = '-';
- }
-
- if (!statii.includes(status)) {
- statii.push(status);
- }
- });
-
- // Multiple status codes
- if (statii.length > 1) {
- return "-";
- } else if (statii.length == 1) {
- return stockStatusDisplay(statii[0]);
- } else {
- return "-";
- }
- } else if (field == 'batch') {
- var batches = [];
-
- data.forEach(function(item) {
- var batch = item.batch;
-
- if (!batch || batch == '') {
- batch = '-';
- }
-
- if (!batches.includes(batch)) {
- batches.push(batch);
- }
- });
-
- if (batches.length > 1) {
- return "" + batches.length + " batches";
- } else if (batches.length == 1) {
- if (batches[0]) {
- return batches[0];
- } else {
- return '-';
- }
- } else {
- return '-';
- }
- } else if (field == 'location__path') {
- /* Determine how many locations */
- var locations = [];
-
- data.forEach(function(item) {
- var loc = item.location;
-
- if (!locations.includes(loc)) {
- locations.push(loc);
- }
- });
-
- if (locations.length > 1) {
- return "In " + locations.length + " locations";
- } else {
- // A single location!
- return renderLink(row.location__path, '/stock/location/' + row.location + '/')
- }
- } else if (field == 'notes') {
- var notes = [];
-
- data.forEach(function(item) {
- var note = item.notes;
-
- if (!note || note == '') {
- note = '-';
- }
-
- if (!notes.includes(note)) {
- notes.push(note);
- }
- });
-
- if (notes.length > 1) {
- return '...';
- } else if (notes.length == 1) {
- return notes[0] || '-';
- } else {
- return '-';
- }
- }
- else {
- return '';
- }
- },
- columns: [
- {
- checkbox: true,
- title: 'Select',
- searchable: false,
- },
- {
- field: 'pk',
- title: 'ID',
- visible: false,
- },
- {
- field: 'part_name',
- title: 'Part',
- sortable: true,
- formatter: function(value, row, index, field) {
-
- var url = '';
- var thumb = row.part_detail.thumbnail;
- var name = row.part_detail.full_name;
-
- if (row.supplier_part) {
- url = `/supplier-part/${row.supplier_part}/`;
- } else {
- url = `/part/${row.part}/`;
- }
-
- html = imageHoverIcon(thumb) + renderLink(name, url);
-
- return html;
- }
- },
- {
- field: 'part_description',
- title: 'Description',
- sortable: true,
- formatter: function(value, row, index, field) {
- return row.part_detail.description;
- }
- },
- {
- field: 'quantity',
- title: 'Stock',
- sortable: true,
- formatter: function(value, row, index, field) {
-
- var val = parseFloat(value);
-
- // If there is a single unit with a serial number, use the serial number
- if (row.serial && row.quantity == 1) {
- val = '# ' + row.serial;
- } else {
- val = +val.toFixed(5);
- }
-
- var html = renderLink(val, `/stock/item/${row.pk}/`);
-
- if (row.allocated) {
- html += ``;
- }
-
- // 70 = "LOST"
- if (row.status == 70) {
- html += ``;
- }
-
- return html;
- }
- },
- {
- field: 'status',
- title: 'Status',
- sortable: 'true',
- formatter: function(value, row, index, field) {
- return stockStatusDisplay(value);
- },
- },
- {
- field: 'batch',
- title: 'Batch',
- sortable: true,
- },
- {
- field: 'location_detail.pathstring',
- title: 'Location',
- sortable: true,
- formatter: function(value, row, index, field) {
- if (value) {
- return renderLink(value, '/stock/location/' + row.location + '/');
- }
- else {
- return 'No stock location set';
- }
- }
- },
- {
- field: 'notes',
- title: 'Notes',
- }
- ],
- });
-
- if (options.buttons) {
- linkButtonsToSelection(table, options.buttons);
- }
-
- function stockAdjustment(action) {
- var items = $("#stock-table").bootstrapTable("getSelections");
-
- var stock = [];
-
- items.forEach(function(item) {
- stock.push(item.pk);
- });
-
- // Buttons for launching secondary modals
- var secondary = [];
-
- if (action == 'move') {
- secondary.push({
- field: 'destination',
- label: 'New Location',
- title: 'Create new location',
- url: "/stock/location/new/",
- });
- }
-
- launchModalForm("/stock/adjust/",
- {
- data: {
- action: action,
- stock: stock,
- },
- success: function() {
- $("#stock-table").bootstrapTable('refresh');
- },
- secondary: secondary,
- }
- );
- }
-
- // Automatically link button callbacks
- $('#multi-item-stocktake').click(function() {
- stockAdjustment('count');
- });
-
- $('#multi-item-remove').click(function() {
- stockAdjustment('take');
- });
-
- $('#multi-item-add').click(function() {
- stockAdjustment('add');
- });
-
- $("#multi-item-move").click(function() {
- stockAdjustment('move');
- });
-
- $("#multi-item-order").click(function() {
- var selections = $("#stock-table").bootstrapTable("getSelections");
-
- var stock = [];
-
- selections.forEach(function(item) {
- stock.push(item.pk);
- });
-
- launchModalForm("/order/purchase-order/order-parts/", {
- data: {
- stock: stock,
- },
- });
- });
-
- $("#multi-item-delete").click(function() {
- var selections = $("#stock-table").bootstrapTable("getSelections");
-
- var stock = [];
-
- selections.forEach(function(item) {
- stock.push(item.pk);
- });
-
- stockAdjustment('delete');
- });
-}
-
-
function loadStockTrackingTable(table, options) {
var cols = [
diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py
index a3a8950401..61ca4afbc9 100644
--- a/InvenTree/InvenTree/urls.py
+++ b/InvenTree/InvenTree/urls.py
@@ -76,6 +76,7 @@ settings_urls = [
dynamic_javascript_urls = [
url(r'^part.js', DynamicJsView.as_view(template_name='js/part.js'), name='part.js'),
+ url(r'^stock.js', DynamicJsView.as_view(template_name='js/stock.js'), name='stock.js'),
]
urlpatterns = [
diff --git a/InvenTree/templates/base.html b/InvenTree/templates/base.html
index 9d8f650c69..f734d47eba 100644
--- a/InvenTree/templates/base.html
+++ b/InvenTree/templates/base.html
@@ -115,6 +115,7 @@ InvenTree
+
diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js
new file mode 100644
index 0000000000..d986a19246
--- /dev/null
+++ b/InvenTree/templates/js/stock.js
@@ -0,0 +1,360 @@
+{% load i18n %}
+
+function loadStockTable(table, options) {
+ /* Load data into a stock table with adjustable options.
+ * Fetches data (via AJAX) and loads into a bootstrap table.
+ * Also links in default button callbacks.
+ *
+ * Options:
+ * url - URL for the stock query
+ * params - query params for augmenting stock data request
+ * groupByField - Column for grouping stock items
+ * buttons - Which buttons to link to stock selection callbacks
+ * filterList - element where filters are displayed
+ * disableFilters: If true, disable custom filters
+ */
+
+ // List of user-params which override the default filters
+
+ options.params['part_detail'] = true;
+ options.params['location_detail'] = true;
+
+ var params = options.params || {};
+
+ var filterListElement = options.filterList || "#filter-list-stock";
+
+ var filters = {};
+
+ if (!options.disableFilters) {
+ filters = loadTableFilters("stock");
+ }
+
+ var original = {};
+
+ for (var key in params) {
+ original[key] = params[key];
+ }
+
+ setupFilterList("stock", table, filterListElement);
+
+ // Override the default values, or add new ones
+ for (var key in params) {
+ filters[key] = params[key];
+ }
+
+ table.inventreeTable({
+ method: 'get',
+ formatNoMatches: function() {
+ return '{% trans "No stock items matching query" %}';
+ },
+ url: options.url,
+ queryParams: filters,
+ customSort: customGroupSorter,
+ groupBy: true,
+ original: original,
+ groupByField: options.groupByField || 'part',
+ groupByFormatter: function(field, id, data) {
+
+ var row = data[0];
+
+ if (field == 'part_name') {
+
+ var name = row.part_detail.full_name;
+
+ return imageHoverIcon(row.part_detail.thumbnail) + name + ' (' + data.length + ' items)';
+ }
+ else if (field == 'part_description') {
+ return row.part_detail.description;
+ }
+ else if (field == 'quantity') {
+ var stock = 0;
+ var items = 0;
+
+ data.forEach(function(item) {
+ stock += parseFloat(item.quantity);
+ items += 1;
+ });
+
+ stock = +stock.toFixed(5);
+
+ return stock + " (" + items + " items)";
+ } else if (field == 'status') {
+ var statii = [];
+
+ data.forEach(function(item) {
+ var status = String(item.status);
+
+ if (!status || status == '') {
+ status = '-';
+ }
+
+ if (!statii.includes(status)) {
+ statii.push(status);
+ }
+ });
+
+ // Multiple status codes
+ if (statii.length > 1) {
+ return "-";
+ } else if (statii.length == 1) {
+ return stockStatusDisplay(statii[0]);
+ } else {
+ return "-";
+ }
+ } else if (field == 'batch') {
+ var batches = [];
+
+ data.forEach(function(item) {
+ var batch = item.batch;
+
+ if (!batch || batch == '') {
+ batch = '-';
+ }
+
+ if (!batches.includes(batch)) {
+ batches.push(batch);
+ }
+ });
+
+ if (batches.length > 1) {
+ return "" + batches.length + " batches";
+ } else if (batches.length == 1) {
+ if (batches[0]) {
+ return batches[0];
+ } else {
+ return '-';
+ }
+ } else {
+ return '-';
+ }
+ } else if (field == 'location__path') {
+ /* Determine how many locations */
+ var locations = [];
+
+ data.forEach(function(item) {
+ var loc = item.location;
+
+ if (!locations.includes(loc)) {
+ locations.push(loc);
+ }
+ });
+
+ if (locations.length > 1) {
+ return "In " + locations.length + " locations";
+ } else {
+ // A single location!
+ return renderLink(row.location__path, '/stock/location/' + row.location + '/')
+ }
+ } else if (field == 'notes') {
+ var notes = [];
+
+ data.forEach(function(item) {
+ var note = item.notes;
+
+ if (!note || note == '') {
+ note = '-';
+ }
+
+ if (!notes.includes(note)) {
+ notes.push(note);
+ }
+ });
+
+ if (notes.length > 1) {
+ return '...';
+ } else if (notes.length == 1) {
+ return notes[0] || '-';
+ } else {
+ return '-';
+ }
+ }
+ else {
+ return '';
+ }
+ },
+ columns: [
+ {
+ checkbox: true,
+ title: '{% trans "Select" %}',
+ searchable: false,
+ },
+ {
+ field: 'pk',
+ title: 'ID',
+ visible: false,
+ },
+ {
+ field: 'part_name',
+ title: '{% trans "Part" %}',
+ sortable: true,
+ formatter: function(value, row, index, field) {
+
+ var url = '';
+ var thumb = row.part_detail.thumbnail;
+ var name = row.part_detail.full_name;
+
+ if (row.supplier_part) {
+ url = `/supplier-part/${row.supplier_part}/`;
+ } else {
+ url = `/part/${row.part}/`;
+ }
+
+ html = imageHoverIcon(thumb) + renderLink(name, url);
+
+ return html;
+ }
+ },
+ {
+ field: 'part_description',
+ title: '{% trans "Description" %}',
+ sortable: true,
+ formatter: function(value, row, index, field) {
+ return row.part_detail.description;
+ }
+ },
+ {
+ field: 'quantity',
+ title: '{% trans "Stock" %}',
+ sortable: true,
+ formatter: function(value, row, index, field) {
+
+ var val = parseFloat(value);
+
+ // If there is a single unit with a serial number, use the serial number
+ if (row.serial && row.quantity == 1) {
+ val = '# ' + row.serial;
+ } else {
+ val = +val.toFixed(5);
+ }
+
+ var html = renderLink(val, `/stock/item/${row.pk}/`);
+
+ if (row.allocated) {
+ html += ``;
+ }
+
+ // 70 = "LOST"
+ if (row.status == 70) {
+ html += ``;
+ }
+
+ return html;
+ }
+ },
+ {
+ field: 'status',
+ title: '{% trans "Status" %}',
+ sortable: 'true',
+ formatter: function(value, row, index, field) {
+ return stockStatusDisplay(value);
+ },
+ },
+ {
+ field: 'batch',
+ title: '{% trans "Batch" %}',
+ sortable: true,
+ },
+ {
+ field: 'location_detail.pathstring',
+ title: '{% trans "Location" %}',
+ sortable: true,
+ formatter: function(value, row, index, field) {
+ if (value) {
+ return renderLink(value, '/stock/location/' + row.location + '/');
+ }
+ else {
+ return '{% trans "No stock location set" %}';
+ }
+ }
+ },
+ {
+ field: 'notes',
+ title: '{% trans "Notes" %}',
+ }
+ ],
+ });
+
+ if (options.buttons) {
+ linkButtonsToSelection(table, options.buttons);
+ }
+
+ function stockAdjustment(action) {
+ var items = $("#stock-table").bootstrapTable("getSelections");
+
+ var stock = [];
+
+ items.forEach(function(item) {
+ stock.push(item.pk);
+ });
+
+ // Buttons for launching secondary modals
+ var secondary = [];
+
+ if (action == 'move') {
+ secondary.push({
+ field: 'destination',
+ label: 'New Location',
+ title: 'Create new location',
+ url: "/stock/location/new/",
+ });
+ }
+
+ launchModalForm("/stock/adjust/",
+ {
+ data: {
+ action: action,
+ stock: stock,
+ },
+ success: function() {
+ $("#stock-table").bootstrapTable('refresh');
+ },
+ secondary: secondary,
+ }
+ );
+ }
+
+ // Automatically link button callbacks
+ $('#multi-item-stocktake').click(function() {
+ stockAdjustment('count');
+ });
+
+ $('#multi-item-remove').click(function() {
+ stockAdjustment('take');
+ });
+
+ $('#multi-item-add').click(function() {
+ stockAdjustment('add');
+ });
+
+ $("#multi-item-move").click(function() {
+ stockAdjustment('move');
+ });
+
+ $("#multi-item-order").click(function() {
+ var selections = $("#stock-table").bootstrapTable("getSelections");
+
+ var stock = [];
+
+ selections.forEach(function(item) {
+ stock.push(item.pk);
+ });
+
+ launchModalForm("/order/purchase-order/order-parts/", {
+ data: {
+ stock: stock,
+ },
+ });
+ });
+
+ $("#multi-item-delete").click(function() {
+ var selections = $("#stock-table").bootstrapTable("getSelections");
+
+ var stock = [];
+
+ selections.forEach(function(item) {
+ stock.push(item.pk);
+ });
+
+ stockAdjustment('delete');
+ });
+}