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:
Oliver 2023-09-15 10:04:54 +10:00 committed by GitHub
parent 56fdbc00c9
commit c8021ec319
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 75 additions and 1 deletions

View File

@ -1186,7 +1186,8 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
BuildLine.objects.bulk_create(lines)
logger.info(f"Created {len(lines)} BuildLine objects for BuildOrder")
if len(lines) > 0:
logger.info(f"Created {len(lines)} BuildLine objects for BuildOrder")
@transaction.atomic
def update_build_line_items(self):

View File

@ -24,6 +24,55 @@ import part.models as part_models
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):
"""Check the required stock for a newly created build order.

View File

@ -3749,6 +3749,18 @@ class BomItem(DataImportMixin, MetadataMixin, models.Model):
"""Return the list API endpoint URL associated with the BomItem model"""
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):
"""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)
@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=PartSellPriceBreak, dispatch_uid='post_save_sale_price_break')
@receiver(post_save, sender=PartInternalPriceBreak, dispatch_uid='post_save_internal_price_break')