diff --git a/.github/ISSUE_TEMPLATE/app_issue.md b/.github/ISSUE_TEMPLATE/app_issue.md
deleted file mode 100644
index e71861394c..0000000000
--- a/.github/ISSUE_TEMPLATE/app_issue.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-name: App issue
-about: Report a bug or issue with the InvenTree app
-title: "[APP] Enter bug description"
-labels: bug, app
-assignees: ''
-
----
-
-**Describe the bug**
-A clear and concise description of the bug or issue
-
-**To Reproduce**
-Steps to reproduce the behavior:
-
-1. Go to ...
-2. Select ...
-3. ...
-
-**Expected Behavior**
-A clear and concise description of what you expected to happen
-
-**Screenshots**
-If applicable, add screenshots to help explain your problem
-
-**Version Information**
-
-- App platform: *Select iOS or Android*
-- App version: *Enter app version*
-- Server version: *Enter server version*
diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py
index 33a4aa3c0e..f302c84d6f 100644
--- a/InvenTree/InvenTree/version.py
+++ b/InvenTree/InvenTree/version.py
@@ -88,9 +88,6 @@ def isInvenTreeDevelopmentVersion():
"""
Return True if current InvenTree version is a "development" version
"""
-
- print("is dev?", inventreeVersion())
-
return inventreeVersion().endswith('dev')
diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py
index 017457f600..eaa65dd763 100644
--- a/InvenTree/stock/api.py
+++ b/InvenTree/stock/api.py
@@ -109,6 +109,17 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
return super().update(request, *args, **kwargs)
+ def perform_destroy(self, instance):
+ """
+ Instead of "deleting" the StockItem
+ (which may take a long time)
+ we instead schedule it for deletion at a later date.
+
+ The background worker will delete these in the future
+ """
+
+ instance.mark_for_deletion()
+
class StockAdjust(APIView):
"""
diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py
index 3344dec0eb..1372e63406 100644
--- a/InvenTree/stock/models.py
+++ b/InvenTree/stock/models.py
@@ -1650,9 +1650,6 @@ def before_delete_stock_item(sender, instance, using, **kwargs):
child.parent = instance.parent
child.save()
- # Rebuild the MPTT tree
- StockItem.objects.rebuild()
-
class StockItemAttachment(InvenTreeAttachment):
"""
diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py
index 619b4444d7..21c355fae2 100644
--- a/InvenTree/stock/test_api.py
+++ b/InvenTree/stock/test_api.py
@@ -18,6 +18,7 @@ from InvenTree.api_tester import InvenTreeAPITestCase
from common.models import InvenTreeSetting
from .models import StockItem, StockLocation
+from .tasks import delete_old_stock_items
class StockAPITestCase(InvenTreeAPITestCase):
@@ -37,6 +38,7 @@ class StockAPITestCase(InvenTreeAPITestCase):
'stock.add',
'stock_location.change',
'stock_location.add',
+ 'stock.delete',
]
def setUp(self):
@@ -591,6 +593,60 @@ class StocktakeTest(StockAPITestCase):
self.assertContains(response, 'Valid location must be specified', status_code=status.HTTP_400_BAD_REQUEST)
+class StockItemDeletionTest(StockAPITestCase):
+ """
+ Tests for stock item deletion via the API
+ """
+
+ def test_delete(self):
+
+ # Check there are no stock items scheduled for deletion
+ self.assertEqual(
+ StockItem.objects.filter(scheduled_for_deletion=True).count(),
+ 0
+ )
+
+ # Create and then delete a bunch of stock items
+ for idx in range(10):
+
+ # Create new StockItem via the API
+ response = self.post(
+ reverse('api-stock-list'),
+ {
+ 'part': 1,
+ 'location': 1,
+ 'quantity': idx,
+ },
+ expected_code=201
+ )
+
+ pk = response.data['pk']
+
+ item = StockItem.objects.get(pk=pk)
+
+ self.assertFalse(item.scheduled_for_deletion)
+
+ # Request deletion via the API
+ self.delete(
+ reverse('api-stock-detail', kwargs={'pk': pk}),
+ expected_code=204
+ )
+
+ # There should be 100x StockItem objects marked for deletion
+ self.assertEqual(
+ StockItem.objects.filter(scheduled_for_deletion=True).count(),
+ 10
+ )
+
+ # Perform the actual delete (will take some time)
+ delete_old_stock_items()
+
+ self.assertEqual(
+ StockItem.objects.filter(scheduled_for_deletion=True).count(),
+ 0
+ )
+
+
class StockTestResultTest(StockAPITestCase):
def get_url(self):
diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js
index bcfa7ef5ff..8f571df02e 100644
--- a/InvenTree/templates/js/translated/bom.js
+++ b/InvenTree/templates/js/translated/bom.js
@@ -271,7 +271,7 @@ function loadBomTable(table, options) {
sortable: true,
formatter: function(value, row) {
- var url = `/part/${row.sub_part_detail.pk}/?display=stock`;
+ var url = `/part/${row.sub_part_detail.pk}/?display=part-stock`;
var text = value;
if (value == null || value <= 0) {
diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js
index 9abbd2a0b9..aba3c46330 100644
--- a/InvenTree/templates/js/translated/part.js
+++ b/InvenTree/templates/js/translated/part.js
@@ -94,7 +94,12 @@ function partFields(options={}) {
},
default_location: {
},
- default_supplier: {},
+ default_supplier: {
+ filters: {
+ part_detail: true,
+ supplier_detail: true,
+ }
+ },
default_expiry: {
icon: 'fa-calendar-alt',
},
@@ -315,6 +320,9 @@ function editPart(pk) {
edit: true
});
+ // Filter supplied parts by the Part ID
+ fields.default_supplier.filters.part = pk;
+
var groups = partGroups({});
constructForm(url, {
@@ -338,6 +346,9 @@ function duplicatePart(pk, options={}) {
duplicate: pk,
});
+ // Remove "default_supplier" field
+ delete fields['default_supplier'];
+
// If we are making a "variant" part
if (options.variant) {
@@ -528,7 +539,7 @@ function loadPartVariantTable(table, partId, options={}) {
field: 'in_stock',
title: '{% trans "Stock" %}',
formatter: function(value, row) {
- return renderLink(value, `/part/${row.pk}/?display=stock`);
+ return renderLink(value, `/part/${row.pk}/?display=part-stock`);
}
}
];
@@ -934,7 +945,7 @@ function loadPartTable(table, url, options={}) {
title: '{% trans "Stock" %}',
searchable: false,
formatter: function(value, row) {
- var link = 'stock';
+ var link = '?display=part-stock';
if (value) {
// There IS stock available for this part
@@ -947,17 +958,17 @@ function loadPartTable(table, url, options={}) {
} else if (row.on_order) {
// There is no stock available, but stock is on order
value = `0{% trans "On Order" %}: ${row.on_order}`;
- link = 'orders';
+ link = '?display=purchase-orders';
} else if (row.building) {
// There is no stock available, but stock is being built
value = `0{% trans "Building" %}: ${row.building}`;
- link = 'builds';
+ link = '?display=build-orders';
} else {
// There is no stock available
value = `0{% trans "No Stock" %}`;
}
- return renderLink(value, `/part/${row.pk}/${link}/`);
+ return renderLink(value, `/part/${row.pk}/${link}`);
}
};
diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js
index e35831624e..17c2598d1b 100644
--- a/InvenTree/templates/js/translated/stock.js
+++ b/InvenTree/templates/js/translated/stock.js
@@ -1019,7 +1019,7 @@ function loadStockTable(table, options) {
return '-';
}
- var link = `/supplier-part/${row.supplier_part}/?display=stock`;
+ var link = `/supplier-part/${row.supplier_part}/?display=part-stock`;
var text = '';