mirror of
https://github.com/inventree/InvenTree.git
synced 2025-09-13 14:11:37 +00:00
Bom total quantity (#10142)
* Add 'total quantity' field to BOM exporter * Extend unit test * Update docs image
This commit is contained in:
Binary file not shown.
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 43 KiB |
@@ -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(
|
||||
|
@@ -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."""
|
||||
|
Reference in New Issue
Block a user