From 44008f33e247bb856923d68b2b3fc9e7ff8b7dd3 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Feb 2022 15:40:49 +1100 Subject: [PATCH 01/12] Refactoring Build model functions - Determining if a build order is correctly allocated has become more complex - Complex BOM behaviours (e.g. variants, templates, and substitutes) have made it more difficult! - Recently, a reference to the defining BomItem object was added to the BuildItem model - Now, a simpler way is to check allocation against the parent BomItem - It is much better, but means that a lot of refactoring and testing will be required! --- InvenTree/build/models.py | 101 ++++++------------ InvenTree/build/serializers.py | 4 +- .../build/templates/build/build_base.html | 4 +- InvenTree/build/templates/build/detail.html | 2 +- InvenTree/build/test_build.py | 38 +++---- 5 files changed, 55 insertions(+), 94 deletions(-) diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 74b75787e7..f37b55876c 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -479,8 +479,6 @@ class Build(MPTTModel, ReferenceIndexingMixin): outputs = self.get_build_outputs(complete=True) - # TODO - Ordering? - return outputs @property @@ -491,8 +489,6 @@ class Build(MPTTModel, ReferenceIndexingMixin): outputs = self.get_build_outputs(complete=False) - # TODO - Order by how "complete" they are? - return outputs @property @@ -563,7 +559,7 @@ class Build(MPTTModel, ReferenceIndexingMixin): if self.remaining > 0: return False - if not self.areUntrackedPartsFullyAllocated(): + if not self.are_untracked_parts_allocated(): return False # No issues! @@ -584,7 +580,7 @@ class Build(MPTTModel, ReferenceIndexingMixin): self.save() # Remove untracked allocated stock - self.subtractUntrackedStock(user) + self.subtract_allocated_stock(user) # Ensure that there are no longer any BuildItem objects # which point to thisFcan Build Order @@ -768,7 +764,7 @@ class Build(MPTTModel, ReferenceIndexingMixin): output.delete() @transaction.atomic - def subtractUntrackedStock(self, user): + def subtract_allocated_stock(self, user): """ Called when the Build is marked as "complete", this function removes the allocated untracked items from stock. @@ -831,7 +827,7 @@ class Build(MPTTModel, ReferenceIndexingMixin): self.save() - def requiredQuantity(self, part, output): + def required_quantity(self, bom_item, output=None): """ Get the quantity of a part required to complete the particular build output. @@ -840,46 +836,41 @@ class Build(MPTTModel, ReferenceIndexingMixin): output - The particular build output (StockItem) """ - # Extract the BOM line item from the database - try: - bom_item = PartModels.BomItem.objects.get(part=self.part.pk, sub_part=part.pk) - quantity = bom_item.quantity - except (PartModels.BomItem.DoesNotExist): - quantity = 0 + quantity = bom_item.quantity if output: - quantity *= output.quantity + quantity *= output.quantity else: quantity *= self.quantity return quantity - def allocatedItems(self, part, output): + def allocated_bom_items(self, bom_item, output=None): """ - Return all BuildItem objects which allocate stock of to + Return all BuildItem objects which allocate stock of to + + Note that the bom_item may allow variants, or direct substitutes, + making things difficult. Args: - part - The part object + bom_item - The BomItem object output - Build output (StockItem). """ - # Remember, if 'variant' stock is allowed to be allocated, it becomes more complicated! - variants = part.get_descendants(include_self=True) - allocations = BuildItem.objects.filter( build=self, - stock_item__part__pk__in=[p.pk for p in variants], + bom_item=bom_item, install_into=output, ) return allocations - def allocatedQuantity(self, part, output): + def allocated_quantity(self, bom_item, output=None): """ Return the total quantity of given part allocated to a given build output. """ - allocations = self.allocatedItems(part, output) + allocations = self.allocated_bom_items(bom_item, output) allocated = allocations.aggregate( q=Coalesce( @@ -891,24 +882,24 @@ class Build(MPTTModel, ReferenceIndexingMixin): return allocated['q'] - def unallocatedQuantity(self, part, output): + def unallocated_quantity(self, bom_item, output=None): """ Return the total unallocated (remaining) quantity of a part against a particular output. """ - required = self.requiredQuantity(part, output) - allocated = self.allocatedQuantity(part, output) + required = self.required_quantity(bom_item, output) + allocated = self.allocated_quantity(bom_item, output) return max(required - allocated, 0) - def isPartFullyAllocated(self, part, output): + def is_bom_item_allocated(self, bom_item, output=None): """ - Returns True if the part has been fully allocated to the particular build output + Test if the supplied BomItem has been fully allocated! """ - return self.unallocatedQuantity(part, output) == 0 + return self.unallocated_quantity(bom_item, output) == 0 - def isFullyAllocated(self, output, verbose=False): + def is_fully_allocated(self, output, verbose=False): """ Returns True if the particular build output is fully allocated. """ @@ -919,53 +910,24 @@ class Build(MPTTModel, ReferenceIndexingMixin): else: bom_items = self.tracked_bom_items - fully_allocated = True - for bom_item in bom_items: - part = bom_item.sub_part - if not self.isPartFullyAllocated(part, output): - fully_allocated = False - - if verbose: - print(f"Part {part} is not fully allocated for output {output}") - else: - break + if not self.is_bom_item_allocated(bom_item, output): + return False # All parts must be fully allocated! - return fully_allocated + return True - def areUntrackedPartsFullyAllocated(self): + def are_untracked_parts_allocated(self): """ Returns True if the un-tracked parts are fully allocated for this BuildOrder """ - return self.isFullyAllocated(None) + return self.is_fully_allocated(None) - def allocatedParts(self, output): + def unallocated_bom_items(self, output): """ - Return a list of parts which have been fully allocated against a particular output - """ - - allocated = [] - - # If output is not specified, we are talking about "untracked" items - if output is None: - bom_items = self.untracked_bom_items - else: - bom_items = self.tracked_bom_items - - for bom_item in bom_items: - part = bom_item.sub_part - - if self.isPartFullyAllocated(part, output): - allocated.append(part) - - return allocated - - def unallocatedParts(self, output): - """ - Return a list of parts which have *not* been fully allocated against a particular output + Return a list of bom items which have *not* been fully allocated against a particular output """ unallocated = [] @@ -977,10 +939,9 @@ class Build(MPTTModel, ReferenceIndexingMixin): bom_items = self.tracked_bom_items for bom_item in bom_items: - part = bom_item.sub_part - if not self.isPartFullyAllocated(part, output): - unallocated.append(part) + if not self.is_bom_item_allocated(bom_item, output): + unallocated.append(bom_item) return unallocated diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index e708bf0b3b..0c243a8e70 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -160,7 +160,7 @@ class BuildOutputSerializer(serializers.Serializer): if to_complete: # The build output must have all tracked parts allocated - if not build.isFullyAllocated(output): + if not build.is_fully_allocated(output): raise ValidationError(_("This build output is not fully allocated")) return output @@ -436,7 +436,7 @@ class BuildCompleteSerializer(serializers.Serializer): build = self.context['build'] - if not build.areUntrackedPartsFullyAllocated() and not value: + if not build.are_untracked_parts_allocated() and not value: raise ValidationError(_('Required stock has not been fully allocated')) return value diff --git a/InvenTree/build/templates/build/build_base.html b/InvenTree/build/templates/build/build_base.html index 7340f1486d..cd7126a801 100644 --- a/InvenTree/build/templates/build/build_base.html +++ b/InvenTree/build/templates/build/build_base.html @@ -125,7 +125,7 @@ src="{% static 'img/blank_image.png' %}" {% trans "Required build quantity has not yet been completed" %} {% endif %} - {% if not build.areUntrackedPartsFullyAllocated %} + {% if not build.are_untracked_parts_allocated %}
{% trans "Stock has not been fully allocated to this Build Order" %}
@@ -234,7 +234,7 @@ src="{% static 'img/blank_image.png' %}" {% else %} completeBuildOrder({{ build.pk }}, { - allocated: {% if build.areUntrackedPartsFullyAllocated %}true{% else %}false{% endif %}, + allocated: {% if build.are_untracked_parts_allocated %}true{% else %}false{% endif %}, completed: {% if build.remaining == 0 %}true{% else %}false{% endif %}, }); {% endif %} diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html index 28760c5316..f85ec9afa6 100644 --- a/InvenTree/build/templates/build/detail.html +++ b/InvenTree/build/templates/build/detail.html @@ -192,7 +192,7 @@
{% if build.has_untracked_bom_items %} {% if build.active %} - {% if build.areUntrackedPartsFullyAllocated %} + {% if build.are_untracked_parts_allocated %}
{% trans "Untracked stock has been fully allocated for this Build Order" %}
diff --git a/InvenTree/build/test_build.py b/InvenTree/build/test_build.py index 1a1f0b115e..e8578f9fbf 100644 --- a/InvenTree/build/test_build.py +++ b/InvenTree/build/test_build.py @@ -147,15 +147,15 @@ class BuildTest(TestCase): # None of the build outputs have been completed for output in self.build.get_build_outputs().all(): - self.assertFalse(self.build.isFullyAllocated(output)) + self.assertFalse(self.build.is_fully_allocated(output)) - self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_1, self.output_1)) - self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_2, self.output_2)) + self.assertFalse(self.build.is_bom_item_allocated(self.sub_part_1, self.output_1)) + self.assertFalse(self.build.is_bom_item_allocated(self.sub_part_2, self.output_2)) - self.assertEqual(self.build.unallocatedQuantity(self.sub_part_1, self.output_1), 15) - self.assertEqual(self.build.unallocatedQuantity(self.sub_part_1, self.output_2), 35) - self.assertEqual(self.build.unallocatedQuantity(self.sub_part_2, self.output_1), 9) - self.assertEqual(self.build.unallocatedQuantity(self.sub_part_2, self.output_2), 21) + self.assertEqual(self.build.unallocated_quantity(self.sub_part_1, self.output_1), 15) + self.assertEqual(self.build.unallocated_quantity(self.sub_part_1, self.output_2), 35) + self.assertEqual(self.build.unallocated_quantity(self.sub_part_2, self.output_1), 9) + self.assertEqual(self.build.unallocated_quantity(self.sub_part_2, self.output_2), 21) self.assertFalse(self.build.is_complete) @@ -226,7 +226,7 @@ class BuildTest(TestCase): } ) - self.assertTrue(self.build.isFullyAllocated(self.output_1)) + self.assertTrue(self.build.is_fully_allocated(self.output_1)) # Partially allocate tracked stock against build output 2 self.allocate_stock( @@ -236,7 +236,7 @@ class BuildTest(TestCase): } ) - self.assertFalse(self.build.isFullyAllocated(self.output_2)) + self.assertFalse(self.build.is_fully_allocated(self.output_2)) # Partially allocate untracked stock against build self.allocate_stock( @@ -247,9 +247,9 @@ class BuildTest(TestCase): } ) - self.assertFalse(self.build.isFullyAllocated(None, verbose=True)) + self.assertFalse(self.build.is_fully_allocated(None, verbose=True)) - unallocated = self.build.unallocatedParts(None) + unallocated = self.build.unallocated_bom_items(None) self.assertEqual(len(unallocated), 2) @@ -260,19 +260,19 @@ class BuildTest(TestCase): } ) - self.assertFalse(self.build.isFullyAllocated(None, verbose=True)) + self.assertFalse(self.build.is_fully_allocated(None, verbose=True)) - unallocated = self.build.unallocatedParts(None) + unallocated = self.build.unallocated_bom_items(None) self.assertEqual(len(unallocated), 1) self.build.unallocateStock() - unallocated = self.build.unallocatedParts(None) + unallocated = self.build.unallocated_bom_items(None) self.assertEqual(len(unallocated), 2) - self.assertFalse(self.build.areUntrackedPartsFullyAllocated()) + self.assertFalse(self.build.are_untracked_parts_allocated()) # Now we "fully" allocate the untracked untracked items self.allocate_stock( @@ -283,7 +283,7 @@ class BuildTest(TestCase): } ) - self.assertTrue(self.build.areUntrackedPartsFullyAllocated()) + self.assertTrue(self.build.are_untracked_parts_allocated()) def test_cancel(self): """ @@ -331,9 +331,9 @@ class BuildTest(TestCase): } ) - self.assertTrue(self.build.isFullyAllocated(None, verbose=True)) - self.assertTrue(self.build.isFullyAllocated(self.output_1)) - self.assertTrue(self.build.isFullyAllocated(self.output_2)) + self.assertTrue(self.build.is_fully_allocated(None, verbose=True)) + self.assertTrue(self.build.is_fully_allocated(self.output_1)) + self.assertTrue(self.build.is_fully_allocated(self.output_2)) self.build.complete_build_output(self.output_1, None) From ab82f07ef12de00976763c3890e3740d6756175b Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Feb 2022 15:41:10 +1100 Subject: [PATCH 02/12] Bug fix for BuildOutputComplete serializer - Actually use the provided "location" value! --- InvenTree/build/serializers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 0c243a8e70..0a8964ee82 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -404,6 +404,10 @@ class BuildOutputCompleteSerializer(serializers.Serializer): data = self.validated_data + location = data['location'] + status = data['status'] + notes = data.get('notes', '') + outputs = data.get('outputs', []) # Mark the specified build outputs as "complete" @@ -415,8 +419,9 @@ class BuildOutputCompleteSerializer(serializers.Serializer): build.complete_build_output( output, request.user, - status=data['status'], - notes=data.get('notes', '') + location=location, + status=status, + notes=notes, ) From 3dde0dbb2b1e53b47118a30b7bee32fac56c6ae1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Feb 2022 15:43:45 +1100 Subject: [PATCH 03/12] PEP fixes --- InvenTree/build/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index f37b55876c..e48ff35899 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -839,7 +839,7 @@ class Build(MPTTModel, ReferenceIndexingMixin): quantity = bom_item.quantity if output: - quantity *= output.quantity + quantity *= output.quantity else: quantity *= self.quantity From bb164ed72a4775f51e4d301c4ea7f4d9f72acd53 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Feb 2022 15:47:07 +1100 Subject: [PATCH 04/12] Remove old unused functions --- InvenTree/build/models.py | 53 +-------------------------------------- 1 file changed, 1 insertion(+), 52 deletions(-) diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index e48ff35899..cbf55dc265 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -899,7 +899,7 @@ class Build(MPTTModel, ReferenceIndexingMixin): return self.unallocated_quantity(bom_item, output) == 0 - def is_fully_allocated(self, output, verbose=False): + def is_fully_allocated(self, output): """ Returns True if the particular build output is fully allocated. """ @@ -969,57 +969,6 @@ class Build(MPTTModel, ReferenceIndexingMixin): return parts - def availableStockItems(self, part, output): - """ - Returns stock items which are available for allocation to this build. - - Args: - part - Part object - output - The particular build output - """ - - # Grab initial query for items which are "in stock" and match the part - items = StockModels.StockItem.objects.filter( - StockModels.StockItem.IN_STOCK_FILTER - ) - - # Check if variants are allowed for this part - try: - bom_item = PartModels.BomItem.objects.get(part=self.part, sub_part=part) - allow_part_variants = bom_item.allow_variants - except PartModels.BomItem.DoesNotExist: - allow_part_variants = False - - if allow_part_variants: - parts = part.get_descendants(include_self=True) - items = items.filter(part__pk__in=[p.pk for p in parts]) - - else: - items = items.filter(part=part) - - # Exclude any items which have already been allocated - allocated = BuildItem.objects.filter( - build=self, - stock_item__part=part, - install_into=output, - ) - - items = items.exclude( - id__in=[item.stock_item.id for item in allocated.all()] - ) - - # Limit query to stock items which are "downstream" of the source location - if self.take_from is not None: - items = items.filter( - location__in=[loc for loc in self.take_from.getUniqueChildren()] - ) - - # Exclude expired stock items - if not common.models.InvenTreeSetting.get_setting('STOCK_ALLOW_EXPIRED_BUILD'): - items = items.exclude(StockModels.StockItem.EXPIRED_FILTER) - - return items - @property def is_active(self): """ Is this build active? An active build is either: From 9e3406efc9368a94685a944d47e7dda5dcd30f76 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Feb 2022 15:56:09 +1100 Subject: [PATCH 05/12] Add stock tracking code to indicate stock has been consumed by a build order --- InvenTree/InvenTree/status_codes.py | 2 ++ InvenTree/build/models.py | 13 +++++++++++-- InvenTree/stock/models.py | 4 ++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/InvenTree/InvenTree/status_codes.py b/InvenTree/InvenTree/status_codes.py index c8917d679b..ffe22039c9 100644 --- a/InvenTree/InvenTree/status_codes.py +++ b/InvenTree/InvenTree/status_codes.py @@ -258,6 +258,7 @@ class StockHistoryCode(StatusCode): # Build order codes BUILD_OUTPUT_CREATED = 50 BUILD_OUTPUT_COMPLETED = 55 + BUILD_CONSUMED = 57 # Sales order codes @@ -298,6 +299,7 @@ class StockHistoryCode(StatusCode): BUILD_OUTPUT_CREATED: _('Build order output created'), BUILD_OUTPUT_COMPLETED: _('Build order output completed'), + BUILD_CONSUMED: _('Consumed by build order'), RECEIVED_AGAINST_PURCHASE_ORDER: _('Received against purchase order') diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index cbf55dc265..443998ca11 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -1167,7 +1167,12 @@ class BuildItem(models.Model): if item.part.trackable: # Split the allocated stock if there are more available than allocated if item.quantity > self.quantity: - item = item.splitStock(self.quantity, None, user) + item = item.splitStock( + self.quantity, + None, + user, + code=StockHistoryCode.BUILD_CONSUMED, + ) # Make sure we are pointing to the new item self.stock_item = item @@ -1178,7 +1183,11 @@ class BuildItem(models.Model): item.save() else: # Simply remove the items from stock - item.take_stock(self.quantity, user) + item.take_stock( + self.quantity, + user, + code=StockHistoryCode.BUILD_CONSUMED + ) def getStockItemThumbnail(self): """ diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 158d0a2640..45f3a40a4d 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -1530,7 +1530,7 @@ class StockItem(MPTTModel): return True @transaction.atomic - def take_stock(self, quantity, user, notes=''): + def take_stock(self, quantity, user, notes='', code=StockHistoryCode.STOCK_REMOVE): """ Remove items from stock """ @@ -1550,7 +1550,7 @@ class StockItem(MPTTModel): if self.updateQuantity(self.quantity - quantity): self.add_tracking_entry( - StockHistoryCode.STOCK_REMOVE, + code, user, notes=notes, deltas={ From ef9c6e9fe01b95034e2fde99906028bfb4f8d4ab Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Feb 2022 15:56:40 +1100 Subject: [PATCH 06/12] Bug fix --- InvenTree/stock/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 45f3a40a4d..eaae691641 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -31,7 +31,6 @@ from datetime import datetime, timedelta from InvenTree import helpers import InvenTree.tasks -import common.models import report.models import label.models @@ -1311,6 +1310,7 @@ class StockItem(MPTTModel): """ notes = kwargs.get('notes', '') + code = kwargs.get('code', StockHistoryCode.SPLIT_FROM_PARENT) # Do not split a serialized part if self.serialized: @@ -1352,7 +1352,7 @@ class StockItem(MPTTModel): # Add a new tracking item for the new stock item new_stock.add_tracking_entry( - StockHistoryCode.SPLIT_FROM_PARENT, + code, user, notes=notes, deltas={ From fea6091e42ad57c29f28e228a2e64f2ebceaf640 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Feb 2022 15:57:30 +1100 Subject: [PATCH 07/12] Fix incorrect inputs --- InvenTree/build/models.py | 2 -- InvenTree/stock/models.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 443998ca11..095a8cf70c 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -30,8 +30,6 @@ from InvenTree.helpers import increment, getSetting, normalize, MakeBarcode from InvenTree.models import InvenTreeAttachment, ReferenceIndexingMixin from InvenTree.validators import validate_build_order_reference -import common.models - import InvenTree.fields import InvenTree.helpers import InvenTree.tasks diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index eaae691641..42cc5b9f7a 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -31,6 +31,7 @@ from datetime import datetime, timedelta from InvenTree import helpers import InvenTree.tasks +import common.models import report.models import label.models From 88bb0f05e94c4bafd19061d315af168d913cd845 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Feb 2022 16:00:00 +1100 Subject: [PATCH 08/12] URL fixes --- InvenTree/templates/js/translated/stock.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index 10b1b71073..2d84f11e4a 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -1554,11 +1554,11 @@ function locationDetail(row, showLink=true) { } 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/`; + url = `/stock/item/${row.belongs_to}/?display=installed-items`; } else if (row.customer) { // StockItem has been assigned to a customer text = '{% trans "Shipped to customer" %}'; - url = `/company/${row.customer}/assigned-stock/`; + url = `/company/${row.customer}/?display=assigned-stock`; } else if (row.sales_order) { // StockItem has been assigned to a sales order text = '{% trans "Assigned to Sales Order" %}'; From 6bdac076d7df7ff7dcd776e4ab30fd4651422892 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Feb 2022 16:03:44 +1100 Subject: [PATCH 09/12] Adds a checkmark to indicate that a stock item has passed all required tests --- InvenTree/stock/templates/stock/item_base.html | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 7692d632f0..9979468357 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -409,7 +409,14 @@ {% trans "Tests" %} - {{ item.requiredTestStatus.passed }} / {{ item.requiredTestStatus.total }} + + {{ item.requiredTestStatus.passed }} / {{ item.requiredTestStatus.total }} + {% if item.passedAllRequiredTests %} + + {% else %} + + {% endif %} + {% endif %} {% if item.owner %} From d9e1302eaf2d90e066f45e8aeae9c55b765f6e7b Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Feb 2022 16:42:35 +1100 Subject: [PATCH 10/12] Fixes for unit testing --- InvenTree/build/test_build.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/InvenTree/build/test_build.py b/InvenTree/build/test_build.py index e8578f9fbf..ab3a1a8901 100644 --- a/InvenTree/build/test_build.py +++ b/InvenTree/build/test_build.py @@ -247,7 +247,7 @@ class BuildTest(TestCase): } ) - self.assertFalse(self.build.is_fully_allocated(None, verbose=True)) + self.assertFalse(self.build.is_fully_allocated(None)) unallocated = self.build.unallocated_bom_items(None) @@ -260,7 +260,7 @@ class BuildTest(TestCase): } ) - self.assertFalse(self.build.is_fully_allocated(None, verbose=True)) + self.assertFalse(self.build.is_fully_allocated(None)) unallocated = self.build.unallocated_bom_items(None) @@ -331,7 +331,7 @@ class BuildTest(TestCase): } ) - self.assertTrue(self.build.is_fully_allocated(None, verbose=True)) + self.assertTrue(self.build.is_fully_allocated(None)) self.assertTrue(self.build.is_fully_allocated(self.output_1)) self.assertTrue(self.build.is_fully_allocated(self.output_2)) From baab9557561b2405e10ceca1cc73d839428efbe2 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Feb 2022 17:17:34 +1100 Subject: [PATCH 11/12] Further fixes for unit tests --- InvenTree/build/test_build.py | 18 +++++++++--------- .../part/migrations/0056_auto_20201110_1125.py | 7 ++++++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/InvenTree/build/test_build.py b/InvenTree/build/test_build.py index ab3a1a8901..116c705f61 100644 --- a/InvenTree/build/test_build.py +++ b/InvenTree/build/test_build.py @@ -62,20 +62,20 @@ class BuildTest(TestCase): ) # Create BOM item links for the parts - BomItem.objects.create( + self.bom_item_1 = BomItem.objects.create( part=self.assembly, sub_part=self.sub_part_1, quantity=5 ) - BomItem.objects.create( + self.bom_item_2 = BomItem.objects.create( part=self.assembly, sub_part=self.sub_part_2, quantity=3 ) # sub_part_3 is trackable! - BomItem.objects.create( + self.bom_item_3 = BomItem.objects.create( part=self.assembly, sub_part=self.sub_part_3, quantity=2 @@ -149,13 +149,13 @@ class BuildTest(TestCase): for output in self.build.get_build_outputs().all(): self.assertFalse(self.build.is_fully_allocated(output)) - self.assertFalse(self.build.is_bom_item_allocated(self.sub_part_1, self.output_1)) - self.assertFalse(self.build.is_bom_item_allocated(self.sub_part_2, self.output_2)) + self.assertFalse(self.build.is_bom_item_allocated(self.bom_item_1, self.output_1)) + self.assertFalse(self.build.is_bom_item_allocated(self.bom_item_2, self.output_2)) - self.assertEqual(self.build.unallocated_quantity(self.sub_part_1, self.output_1), 15) - self.assertEqual(self.build.unallocated_quantity(self.sub_part_1, self.output_2), 35) - self.assertEqual(self.build.unallocated_quantity(self.sub_part_2, self.output_1), 9) - self.assertEqual(self.build.unallocated_quantity(self.sub_part_2, self.output_2), 21) + self.assertEqual(self.build.unallocated_quantity(self.bom_item_1, self.output_1), 15) + self.assertEqual(self.build.unallocated_quantity(self.bom_item_1, self.output_2), 35) + self.assertEqual(self.build.unallocated_quantity(self.bom_item_2, self.output_1), 9) + self.assertEqual(self.build.unallocated_quantity(self.bom_item_2, self.output_2), 21) self.assertFalse(self.build.is_complete) diff --git a/InvenTree/part/migrations/0056_auto_20201110_1125.py b/InvenTree/part/migrations/0056_auto_20201110_1125.py index e78482db76..efb36b1812 100644 --- a/InvenTree/part/migrations/0056_auto_20201110_1125.py +++ b/InvenTree/part/migrations/0056_auto_20201110_1125.py @@ -1,5 +1,7 @@ # Generated by Django 3.0.7 on 2020-11-10 11:25 +import logging + from django.db import migrations from moneyed import CURRENCIES @@ -7,6 +9,9 @@ from django.db import migrations, connection from company.models import SupplierPriceBreak +logger = logging.getLogger('inventree') + + def migrate_currencies(apps, schema_editor): """ Migrate from the 'old' method of handling currencies, @@ -19,7 +24,7 @@ def migrate_currencies(apps, schema_editor): for the SupplierPriceBreak model, to a new django-money compatible currency. """ - print("Updating currency references for SupplierPriceBreak model...") + logger.info("Updating currency references for SupplierPriceBreak model...") # A list of available currency codes currency_codes = CURRENCIES.keys() From fe4d06313568d5254be65a8d5ab58d5bebe6e9e1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 25 Feb 2022 17:19:39 +1100 Subject: [PATCH 12/12] Change some prints to log messages --- InvenTree/users/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index a95fd21385..c593fb49f3 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -451,7 +451,7 @@ def update_group_roles(group, debug=False): group.permissions.add(permission) if debug: # pragma: no cover - print(f"Adding permission {perm} to group {group.name}") + logger.info(f"Adding permission {perm} to group {group.name}") # Remove any extra permissions from the group for perm in permissions_to_delete: @@ -466,7 +466,7 @@ def update_group_roles(group, debug=False): group.permissions.remove(permission) if debug: # pragma: no cover - print(f"Removing permission {perm} from group {group.name}") + logger.info(f"Removing permission {perm} from group {group.name}") # Enable all action permissions for certain children models # if parent model has 'change' permission @@ -488,7 +488,7 @@ def update_group_roles(group, debug=False): permission = get_permission_object(child_perm) if permission: group.permissions.add(permission) - print(f"Adding permission {child_perm} to group {group.name}") + logger.info(f"Adding permission {child_perm} to group {group.name}") @receiver(post_save, sender=Group, dispatch_uid='create_missing_rule_sets')