mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	Add 'bom_item' field to BuildItem model
- Required to link the build to the output in case of variant stock
This commit is contained in:
		
							
								
								
									
										20
									
								
								InvenTree/build/migrations/0028_builditem_bom_item.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								InvenTree/build/migrations/0028_builditem_bom_item.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					# Generated by Django 3.2 on 2021-06-01 05:23
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					import django.db.models.deletion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('part', '0066_bomitem_allow_variants'),
 | 
				
			||||||
 | 
					        ('build', '0027_auto_20210404_2016'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='builditem',
 | 
				
			||||||
 | 
					            name='bom_item',
 | 
				
			||||||
 | 
					            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='allocate_build_items', to='part.bomitem'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -1036,6 +1036,18 @@ class Build(MPTTModel):
 | 
				
			|||||||
            StockModels.StockItem.IN_STOCK_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)
 | 
					            items = items.filter(part=part)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Exclude any items which have already been allocated
 | 
					        # Exclude any items which have already been allocated
 | 
				
			||||||
@@ -1160,10 +1172,6 @@ class BuildItem(models.Model):
 | 
				
			|||||||
            if self.stock_item.part and self.stock_item.part.trackable and not self.install_into:
 | 
					            if self.stock_item.part and self.stock_item.part.trackable and not self.install_into:
 | 
				
			||||||
                raise ValidationError(_('Build item must specify a build output, as master part is marked as trackable'))
 | 
					                raise ValidationError(_('Build item must specify a build output, as master part is marked as trackable'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Allocated part must be in the BOM for the master part
 | 
					 | 
				
			||||||
            if self.stock_item.part not in self.build.part.getRequiredParts(recursive=False):
 | 
					 | 
				
			||||||
                errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'").format(p=self.build.part.full_name)]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Allocated quantity cannot exceed available stock quantity
 | 
					            # Allocated quantity cannot exceed available stock quantity
 | 
				
			||||||
            if self.quantity > self.stock_item.quantity:
 | 
					            if self.quantity > self.stock_item.quantity:
 | 
				
			||||||
                errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})").format(
 | 
					                errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})").format(
 | 
				
			||||||
@@ -1189,6 +1197,61 @@ class BuildItem(models.Model):
 | 
				
			|||||||
        if len(errors) > 0:
 | 
					        if len(errors) > 0:
 | 
				
			||||||
            raise ValidationError(errors)
 | 
					            raise ValidationError(errors)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Attempt to find the "BomItem" which links this BuildItem to the build.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        - If a BomItem is already set, and it is valid, then we are ok!
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bom_item_valid = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.bom_item:
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
 | 
					            A BomItem object has already been assigned. This is valid if:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            a) It points to the same "part" as the referened build
 | 
				
			||||||
 | 
					            b) Either:
 | 
				
			||||||
 | 
					                i) The sub_part points to the same part as the referenced StockItem
 | 
				
			||||||
 | 
					                ii) The BomItem allows variants and the part referenced by the StockItem
 | 
				
			||||||
 | 
					                    is a variant of the sub_part referenced by the BomItem
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if self.build and self.build.part == self.bom_item.part:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Check that the sub_part points to the stock_item (either directly or via a variant)
 | 
				
			||||||
 | 
					                if self.bom_item.sub_part == self.stock_item.part:
 | 
				
			||||||
 | 
					                    bom_item_valid = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                elif self.bom_item.allow_variants and self.stock_item.part in self.bom_item.sub_part.get_descendants(include_self=False):
 | 
				
			||||||
 | 
					                    bom_item_valid = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # If the existing BomItem is *not* valid, try to find a match
 | 
				
			||||||
 | 
					        if not bom_item_valid:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if self.build and self.stock_item:
 | 
				
			||||||
 | 
					                ancestors = self.stock_item.part.get_ancestors(include_self=True, ascending=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for idx, ancestor in enumerate(ancestors):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    try:
 | 
				
			||||||
 | 
					                        bom_item = PartModels.BomItem.objects.get(part=self.build.part, sub_part=ancestor)
 | 
				
			||||||
 | 
					                    except PartModels.BomItem.DoesNotExist:
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    # A matching BOM item has been found!
 | 
				
			||||||
 | 
					                    if idx == 0 or bom_item.allow_variants:
 | 
				
			||||||
 | 
					                        bom_item_valid = True
 | 
				
			||||||
 | 
					                        self.bom_item = bom_item
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # BomItem did not exist or could not be validated.
 | 
				
			||||||
 | 
					        # Search for a new one
 | 
				
			||||||
 | 
					        if not bom_item_valid:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            raise ValidationError({
 | 
				
			||||||
 | 
					                'stock_item': _("Selected stock item not found in BOM for part '{p}'").format(p=self.build.part.full_name)
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @transaction.atomic
 | 
					    @transaction.atomic
 | 
				
			||||||
    def complete_allocation(self, user):
 | 
					    def complete_allocation(self, user):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@@ -1225,6 +1288,15 @@ class BuildItem(models.Model):
 | 
				
			|||||||
        help_text=_('Build to allocate parts')
 | 
					        help_text=_('Build to allocate parts')
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Internal model which links part <-> sub_part
 | 
				
			||||||
 | 
					    # We need to track this separately, to allow for "variant' stock
 | 
				
			||||||
 | 
					    bom_item = models.ForeignKey(
 | 
				
			||||||
 | 
					        PartModels.BomItem,
 | 
				
			||||||
 | 
					        on_delete=models.CASCADE,
 | 
				
			||||||
 | 
					        related_name='allocate_build_items',
 | 
				
			||||||
 | 
					        blank=True, null=True,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    stock_item = models.ForeignKey(
 | 
					    stock_item = models.ForeignKey(
 | 
				
			||||||
        'stock.StockItem',
 | 
					        'stock.StockItem',
 | 
				
			||||||
        on_delete=models.CASCADE,
 | 
					        on_delete=models.CASCADE,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user