2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-09-13 06:01:35 +00:00

Bom total quantity (#10142)

* Add 'total quantity' field to BOM exporter

* Extend unit test

* Update docs image
This commit is contained in:
Oliver
2025-08-07 10:09:51 +10:00
committed by GitHub
parent 0411b8ca58
commit c4011d0be3
3 changed files with 41 additions and 7 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -2809,7 +2809,7 @@ class BomItemTest(InvenTreeAPITestCase):
)
# Check that the correct exporter plugin has been used
required_cols.extend(['BOM Level'])
required_cols.extend(['BOM Level', 'Total Quantity'])
# Next, download BOM data for a specific sub-assembly, and use the BOM exporter
with self.export_data(

View File

@@ -1,9 +1,13 @@
"""Multi-level BOM exporter plugin."""
from decimal import Decimal
from typing import Optional
from django.utils.translation import gettext_lazy as _
import rest_framework.serializers as serializers
from InvenTree.helpers import normalize
from part.models import BomItem
from plugin import InvenTreePlugin
from plugin.mixins import DataExportMixin
@@ -13,12 +17,20 @@ class BomExporterOptionsSerializer(serializers.Serializer):
"""Custom export options for the BOM exporter plugin."""
export_levels = serializers.IntegerField(
default=1,
default=0,
label=_('Levels'),
help_text=_('Number of levels to export'),
help_text=_(
'Number of levels to export - set to zero to export all BOM levels'
),
min_value=0,
)
export_total_quantity = serializers.BooleanField(
default=True,
label=_('Total Quantity'),
help_text=_('Include total quantity of each part in the BOM'),
)
export_stock_data = serializers.BooleanField(
default=True, label=_('Stock Data'), help_text=_('Include part stock data')
)
@@ -57,7 +69,7 @@ class BomExporterPlugin(DataExportMixin, InvenTreePlugin):
SLUG = 'bom-exporter'
TITLE = _('Multi-Level BOM Exporter')
DESCRIPTION = _('Provides support for exporting multi-level BOMs')
VERSION = '1.0.0'
VERSION = '1.1.0'
AUTHOR = _('InvenTree contributors')
ExportOptionsSerializer = BomExporterOptionsSerializer
@@ -68,6 +80,8 @@ class BomExporterPlugin(DataExportMixin, InvenTreePlugin):
def update_headers(self, headers, context, **kwargs):
"""Update headers for the BOM export."""
export_total_quantity = context.get('export_total_quantity', True)
if not self.export_stock_data:
# Remove stock data from the headers
for field in [
@@ -95,6 +109,10 @@ class BomExporterPlugin(DataExportMixin, InvenTreePlugin):
# Append a "BOM Level" field
headers['level'] = _('BOM Level')
if export_total_quantity:
# Append a 'total quantity' field
headers['total_quantity'] = _('Total Quantity')
# Append variant part columns
if self.export_substitute_data and self.n_substitute_cols > 0:
for idx in range(self.n_substitute_cols):
@@ -167,6 +185,7 @@ class BomExporterPlugin(DataExportMixin, InvenTreePlugin):
self.export_manufacturer_data = context.get('export_manufacturer_data', True)
self.export_substitute_data = context.get('export_substitute_data', True)
self.export_parameter_data = context.get('export_parameter_data', True)
self.export_total_quantity = context.get('export_total_quantity', True)
# Pre-fetch related data to reduce database queries
queryset = self.prefetch_queryset(queryset)
@@ -179,18 +198,23 @@ class BomExporterPlugin(DataExportMixin, InvenTreePlugin):
return self.bom_data
def process_bom_row(self, bom_item, level, **kwargs) -> list:
def process_bom_row(
self, bom_item, level: int = 1, multiplier: Optional[Decimal] = None, **kwargs
) -> list:
"""Process a single BOM row.
Arguments:
bom_item: The BomItem object to process
level: The current level of export
multiplier: The multiplier for the quantity (used for recursive calls)
"""
# Add this row to the output dataset
row = self.serializer_class(bom_item, exporting=True).data
row['level'] = level
if multiplier is None:
multiplier = Decimal(1)
# Extend with additional data
if self.export_substitute_data:
@@ -205,6 +229,11 @@ class BomExporterPlugin(DataExportMixin, InvenTreePlugin):
if self.export_parameter_data:
row.update(self.get_parameter_data(bom_item))
if self.export_total_quantity:
# Calculate the total quantity for this BOM item
total_quantity = Decimal(bom_item.quantity) * multiplier
row['total_quantity'] = normalize(total_quantity)
self.bom_data.append(row)
# If we have reached the maximum export level, return just this bom item
@@ -215,7 +244,12 @@ class BomExporterPlugin(DataExportMixin, InvenTreePlugin):
sub_items = self.prefetch_queryset(sub_items)
for item in sub_items.all():
self.process_bom_row(item, level + 1, **kwargs)
self.process_bom_row(
item,
level=level + 1,
multiplier=multiplier * bom_item.quantity,
**kwargs,
)
def get_substitute_data(self, bom_item: BomItem) -> dict:
"""Return substitute part data for a BomItem."""