mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	Update required parts for build orders (#5542)
- When a BomItem is created or edited, update any active build orders which use it - Runs as a background task - Fixes https://github.com/inventree/InvenTree/issues/5541
This commit is contained in:
		@@ -1186,6 +1186,7 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        BuildLine.objects.bulk_create(lines)
 | 
					        BuildLine.objects.bulk_create(lines)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if len(lines) > 0:
 | 
				
			||||||
            logger.info(f"Created {len(lines)} BuildLine objects for BuildOrder")
 | 
					            logger.info(f"Created {len(lines)} BuildLine objects for BuildOrder")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @transaction.atomic
 | 
					    @transaction.atomic
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,6 +24,55 @@ import part.models as part_models
 | 
				
			|||||||
logger = logging.getLogger('inventree')
 | 
					logger = logging.getLogger('inventree')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def update_build_order_lines(bom_item_pk: int):
 | 
				
			||||||
 | 
					    """Update all BuildOrderLineItem objects which reference a particular BomItem.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    This task is triggered when a BomItem is created or updated.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.info(f"Updating build order lines for BomItem {bom_item_pk}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bom_item = part_models.BomItem.objects.filter(pk=bom_item_pk).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If the BomItem has been deleted, there is nothing to do
 | 
				
			||||||
 | 
					    if not bom_item:
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assemblies = bom_item.get_assemblies()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Find all active builds which reference any of the parts
 | 
				
			||||||
 | 
					    builds = build.models.Build.objects.filter(
 | 
				
			||||||
 | 
					        part__in=list(assemblies),
 | 
				
			||||||
 | 
					        status__in=BuildStatusGroups.ACTIVE_CODES
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Iterate through each build, and update the relevant line items
 | 
				
			||||||
 | 
					    for bo in builds:
 | 
				
			||||||
 | 
					        # Try to find a matching build order line
 | 
				
			||||||
 | 
					        line = build.models.BuildLine.objects.filter(
 | 
				
			||||||
 | 
					            build=bo,
 | 
				
			||||||
 | 
					            bom_item=bom_item,
 | 
				
			||||||
 | 
					        ).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        q = bom_item.get_required_quantity(bo.quantity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if line:
 | 
				
			||||||
 | 
					            # Ensure quantity is correct
 | 
				
			||||||
 | 
					            if line.quantity != q:
 | 
				
			||||||
 | 
					                line.quantity = q
 | 
				
			||||||
 | 
					                line.save()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # Create a new line item
 | 
				
			||||||
 | 
					            build.models.BuildLine.objects.create(
 | 
				
			||||||
 | 
					                build=bo,
 | 
				
			||||||
 | 
					                bom_item=bom_item,
 | 
				
			||||||
 | 
					                quantity=q,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if builds.count() > 0:
 | 
				
			||||||
 | 
					        logger.info(f"Updated {builds.count()} build orders for part {bom_item.part}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_build_stock(build: build.models.Build):
 | 
					def check_build_stock(build: build.models.Build):
 | 
				
			||||||
    """Check the required stock for a newly created build order.
 | 
					    """Check the required stock for a newly created build order.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3749,6 +3749,18 @@ class BomItem(DataImportMixin, MetadataMixin, models.Model):
 | 
				
			|||||||
        """Return the list API endpoint URL associated with the BomItem model"""
 | 
					        """Return the list API endpoint URL associated with the BomItem model"""
 | 
				
			||||||
        return reverse('api-bom-list')
 | 
					        return reverse('api-bom-list')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_assemblies(self):
 | 
				
			||||||
 | 
					        """Return a list of assemblies which use this BomItem"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assemblies = [self.part]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.inherited:
 | 
				
			||||||
 | 
					            assemblies += list(
 | 
				
			||||||
 | 
					                self.part.get_descendants(include_self=False)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return assemblies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_valid_parts_for_allocation(self, allow_variants=True, allow_substitutes=True):
 | 
					    def get_valid_parts_for_allocation(self, allow_variants=True, allow_substitutes=True):
 | 
				
			||||||
        """Return a list of valid parts which can be allocated against this BomItem.
 | 
					        """Return a list of valid parts which can be allocated against this BomItem.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -4048,6 +4060,18 @@ class BomItem(DataImportMixin, MetadataMixin, models.Model):
 | 
				
			|||||||
        return "{pmin} to {pmax}".format(pmin=pmin, pmax=pmax)
 | 
					        return "{pmin} to {pmax}".format(pmin=pmin, pmax=pmax)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@receiver(post_save, sender=BomItem, dispatch_uid='update_bom_build_lines')
 | 
				
			||||||
 | 
					def update_bom_build_lines(sender, instance, created, **kwargs):
 | 
				
			||||||
 | 
					    """Update existing build orders when a BomItem is created or edited"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData():
 | 
				
			||||||
 | 
					        import build.tasks
 | 
				
			||||||
 | 
					        InvenTree.tasks.offload_task(
 | 
				
			||||||
 | 
					            build.tasks.update_build_order_lines,
 | 
				
			||||||
 | 
					            instance.pk
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@receiver(post_save, sender=BomItem, dispatch_uid='post_save_bom_item')
 | 
					@receiver(post_save, sender=BomItem, dispatch_uid='post_save_bom_item')
 | 
				
			||||||
@receiver(post_save, sender=PartSellPriceBreak, dispatch_uid='post_save_sale_price_break')
 | 
					@receiver(post_save, sender=PartSellPriceBreak, dispatch_uid='post_save_sale_price_break')
 | 
				
			||||||
@receiver(post_save, sender=PartInternalPriceBreak, dispatch_uid='post_save_internal_price_break')
 | 
					@receiver(post_save, sender=PartInternalPriceBreak, dispatch_uid='post_save_internal_price_break')
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user