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 = '';