From ef23ab1abc075be8149ff9f2884a565e582c76ff Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 18 Feb 2021 17:59:04 +1100 Subject: [PATCH 1/4] Adds functionality for traversing "through" installed items to extract test results --- InvenTree/stock/models.py | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 84cc696593..5d7cce5132 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -699,6 +699,36 @@ class StockItem(MPTTModel): return True + def get_installed_items(self, cascade=False): + """ + Return all stock items which are *installed* in this one! + + Args: + cascade - Include items which are installed in items which are installed in items + + Note: This function is recursive, and may result in a number of database hits! + """ + + installed = set() + + items = StockItem.objects.filter(belongs_to=self) + + for item in items: + + # Prevent recursion + if item not in installed: + installed.add(item) + + if cascade: + sub_items = item.get_installed_items(cascade=True) + + for sub_item in sub_items: + # Prevent recursion + if sub_item not in installed: + installed.add(sub_item) + + return installed + def installedItemCount(self): """ Return the number of stock items installed inside this one. @@ -1286,6 +1316,23 @@ class StockItem(MPTTModel): key = helpers.generateTestKey(result.test) result_map[key] = result + # Do we wish to include test results from installed items? + include_installed = kwargs.get('include_installed', False) + + # Do we wish to "cascade" and include test results from installed stock items? + cascade = kwargs.get('cascade', False) + + if include_installed: + installed_items = get_installed_items(cascade=cascade) + + for item in installed_items: + item_results = item.testResultMap + + for key in item_results.keys(): + # Results from sub items should not override master ones + if key not in result_map.keys(): + result_map[key] = item_results[key] + return result_map def testResultList(self, **kwargs): From a9f255be853fcc355a061db6e52b4483eff9607c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 18 Feb 2021 18:01:41 +1100 Subject: [PATCH 2/4] Prevent stock item being added as an installed item inside itself --- InvenTree/stock/models.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 5d7cce5132..2cfaf2ce68 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -714,18 +714,23 @@ class StockItem(MPTTModel): items = StockItem.objects.filter(belongs_to=self) for item in items: + + # Prevent duplication or recursion + if item == self or item in installed: + continue - # Prevent recursion - if item not in installed: - installed.add(item) + installed.add(item) if cascade: sub_items = item.get_installed_items(cascade=True) for sub_item in sub_items: + # Prevent recursion - if sub_item not in installed: - installed.add(sub_item) + if sub_item == self or sub_item in installed: + continue + + installed.add(sub_item) return installed From beeb94785dccb14fbbf315ae48b4960c2c528a83 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 19 Feb 2021 15:50:25 +1100 Subject: [PATCH 3/4] Add option for TestReport to include tests for installed items --- .../0013_testreport_include_installed.py | 18 ++++++++++++++++++ InvenTree/report/models.py | 10 ++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 InvenTree/report/migrations/0013_testreport_include_installed.py diff --git a/InvenTree/report/migrations/0013_testreport_include_installed.py b/InvenTree/report/migrations/0013_testreport_include_installed.py new file mode 100644 index 0000000000..3a535bf172 --- /dev/null +++ b/InvenTree/report/migrations/0013_testreport_include_installed.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2021-02-19 04:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('report', '0012_buildreport'), + ] + + operations = [ + migrations.AddField( + model_name='testreport', + name='include_installed', + field=models.BooleanField(default=False, help_text='Include test results for stock items installed inside assembled item', verbose_name='Include Installed Tests'), + ), + ] diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index 4ab6a25bf4..00777449fc 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -281,6 +281,12 @@ class TestReport(ReportTemplateBase): ] ) + include_installed = models.BooleanField( + default=False, + verbose_name=_('Include Installed Tests'), + help_text=_('Include test results for stock items installed inside assembled item') + ) + def matches_stock_item(self, item): """ Test if this report template matches a given StockItem objects @@ -304,8 +310,8 @@ class TestReport(ReportTemplateBase): return { 'stock_item': stock_item, 'part': stock_item.part, - 'results': stock_item.testResultMap(), - 'result_list': stock_item.testResultList() + 'results': stock_item.testResultMap(include_installed=self.include_installed), + 'result_list': stock_item.testResultList(include_installed=self.include_installed) } From 6037f1452acfd5a63acf475cfe9d4761959433ee Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 19 Feb 2021 15:50:32 +1100 Subject: [PATCH 4/4] Unit testing for new feature --- InvenTree/stock/models.py | 10 +++---- InvenTree/stock/tests.py | 59 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 2cfaf2ce68..4573036732 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -1311,6 +1311,9 @@ class StockItem(MPTTModel): as all named tests are accessible. """ + # Do we wish to include test results from installed items? + include_installed = kwargs.pop('include_installed', False) + # Filter results by "date", so that newer results # will override older ones. results = self.getTestResults(**kwargs).order_by('date') @@ -1321,17 +1324,14 @@ class StockItem(MPTTModel): key = helpers.generateTestKey(result.test) result_map[key] = result - # Do we wish to include test results from installed items? - include_installed = kwargs.get('include_installed', False) - # Do we wish to "cascade" and include test results from installed stock items? cascade = kwargs.get('cascade', False) if include_installed: - installed_items = get_installed_items(cascade=cascade) + installed_items = self.get_installed_items(cascade=cascade) for item in installed_items: - item_results = item.testResultMap + item_results = item.testResultMap() for key in item_results.keys(): # Results from sub items should not override master ones diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index b54411b0d2..f3ca949bbf 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -622,3 +622,62 @@ class TestResultTest(StockTest): item3 = StockItem.objects.get(serial=100, part=item2.part) self.assertEqual(item3.test_results.count(), 4) + + def test_installed_tests(self): + """ + Test test results for stock in stock. + + Or, test "test results" for "stock items" installed "inside" a "stock item" + """ + + # Get a "master" stock item + item = StockItem.objects.get(pk=105) + + tests = item.testResultMap(include_installed=False) + self.assertEqual(len(tests), 3) + + # There are no "sub items" intalled at this stage + tests = item.testResultMap(include_installed=False) + self.assertEqual(len(tests), 3) + + # Create a stock item which is installed *inside* the master item + sub_item = StockItem.objects.create( + part=item.part, + quantity=1, + belongs_to=item, + location=None + ) + + # Now, create some test results against the sub item + + # First test is overshadowed by the same test for the parent part + StockItemTestResult.objects.create( + stock_item=sub_item, + test='firmware version', + date=datetime.datetime.now().date(), + result=True + ) + + # Should return the same number of tests as before + tests = item.testResultMap(include_installed=True) + self.assertEqual(len(tests), 3) + + # Now, add a *unique* test result for the sub item + StockItemTestResult.objects.create( + stock_item=sub_item, + test='some new test', + date=datetime.datetime.now().date(), + result=False, + value='abcde', + ) + + tests = item.testResultMap(include_installed=True) + self.assertEqual(len(tests), 4) + + self.assertIn('somenewtest', tests) + self.assertEqual(sub_item.test_results.count(), 2) + + # Check that asking for test result map for *top item only* still works + tests = item.testResultMap(include_installed=False) + self.assertEqual(len(tests), 3) + self.assertNotIn('somenewtest', tests)