2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-20 22:06:28 +00:00

Merge branch 'master' of https://github.com/inventree/InvenTree into plugin-2037

This commit is contained in:
Matthias
2021-11-10 23:20:39 +01:00
10 changed files with 143 additions and 18 deletions

View File

@ -7,7 +7,7 @@ from collections import OrderedDict
from django.utils.translation import gettext as _
from InvenTree.helpers import DownloadFile, GetExportFormats
from InvenTree.helpers import DownloadFile, GetExportFormats, normalize
from .admin import BomItemResource
from .models import BomItem
@ -59,7 +59,7 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=Fa
uids = []
def add_items(items, level):
def add_items(items, level, cascade):
# Add items at a given layer
for item in items:
@ -71,21 +71,13 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=Fa
bom_items.append(item)
if item.sub_part.assembly:
if cascade and item.sub_part.assembly:
if max_levels is None or level < max_levels:
add_items(item.sub_part.bom_items.all().order_by('id'), level + 1)
if cascade:
# Cascading (multi-level) BOM
top_level_items = part.get_bom_items().order_by('id')
# Start with the top level
items_to_process = part.bom_items.all().order_by('id')
add_items(items_to_process, 1)
else:
# No cascading needed - just the top-level items
bom_items = [item for item in part.bom_items.all().order_by('id')]
add_items(top_level_items, 1, cascade)
dataset = BomItemResource().export(queryset=bom_items, cascade=cascade)
@ -148,8 +140,9 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=Fa
stock_data.append('')
except AttributeError:
stock_data.append('')
# Get part current stock
stock_data.append(str(bom_item.sub_part.available_stock))
stock_data.append(str(normalize(bom_item.sub_part.available_stock)))
for s_idx, header in enumerate(stock_headers):
try:

View File

@ -1392,6 +1392,27 @@ class Part(MPTTModel):
return BomItem.objects.filter(self.get_bom_item_filter(include_inherited=include_inherited))
def get_installed_part_options(self, include_inherited=True, include_variants=True):
"""
Return a set of all Parts which can be "installed" into this part, based on the BOM.
arguments:
include_inherited - If set, include BomItem entries defined for parent parts
include_variants - If set, include variant parts for BomItems which allow variants
"""
parts = set()
for bom_item in self.get_bom_items(include_inherited=include_inherited):
if include_variants and bom_item.allow_variants:
for part in bom_item.sub_part.get_descendants(include_self=True):
parts.add(part)
else:
parts.add(bom_item.sub_part)
return parts
def get_used_in_filter(self, include_inherited=True):
"""
Return a query filter for all parts that this part is used in.

View File

@ -117,6 +117,8 @@ class StockItemResource(ModelResource):
exclude = [
# Exclude MPTT internal model fields
'lft', 'rght', 'tree_id', 'level',
# Exclude internal fields
'serial_int',
]

View File

@ -876,6 +876,7 @@ class StockList(generics.ListCreateAPIView):
ordering_field_aliases = {
'SKU': 'supplier_part__SKU',
'stock': ['quantity', 'serial_int', 'serial'],
}
ordering_fields = [
@ -887,6 +888,7 @@ class StockList(generics.ListCreateAPIView):
'stocktake_date',
'expiry_date',
'quantity',
'stock',
'status',
'SKU',
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.5 on 2021-11-09 23:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('stock', '0067_alter_stockitem_part'),
]
operations = [
migrations.AddField(
model_name='stockitem',
name='serial_int',
field=models.IntegerField(default=0),
),
]

View File

@ -0,0 +1,54 @@
# Generated by Django 3.2.5 on 2021-11-09 23:47
import re
from django.db import migrations
def update_serials(apps, schema_editor):
"""
Rebuild the integer serial number field for existing StockItem objects
"""
StockItem = apps.get_model('stock', 'stockitem')
for item in StockItem.objects.all():
if item.serial is None:
# Skip items without existing serial numbers
continue
serial = 0
result = re.match(r"^(\d+)", str(item.serial))
if result and len(result.groups()) == 1:
try:
serial = int(result.groups()[0])
except:
serial = 0
item.serial_int = serial
item.save()
def nupdate_serials(apps, schema_editor):
"""
Provided only for reverse migration compatibility
"""
pass
class Migration(migrations.Migration):
dependencies = [
('stock', '0068_stockitem_serial_int'),
]
operations = [
migrations.RunPython(
update_serials,
reverse_code=nupdate_serials,
)
]

View File

@ -7,6 +7,7 @@ Stock database model definitions
from __future__ import unicode_literals
import os
import re
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError, FieldError
@ -223,6 +224,32 @@ class StockItem(MPTTModel):
self.scheduled_for_deletion = True
self.save()
def update_serial_number(self):
"""
Update the 'serial_int' field, to be an integer representation of the serial number.
This is used for efficient numerical sorting
"""
serial = getattr(self, 'serial', '')
# Default value if we cannot convert to an integer
serial_int = 0
if serial is not None:
serial = str(serial)
# Look at the start of the string - can it be "integerized"?
result = re.match(r'^(\d+)', serial)
if result and len(result.groups()) == 1:
try:
serial_int = int(result.groups()[0])
except:
serial_int = 0
self.serial_int = serial_int
def save(self, *args, **kwargs):
"""
Save this StockItem to the database. Performs a number of checks:
@ -234,6 +261,8 @@ class StockItem(MPTTModel):
self.validate_unique()
self.clean()
self.update_serial_number()
user = kwargs.pop('user', None)
# If 'add_note = False' specified, then no tracking note will be added for item creation
@ -504,6 +533,8 @@ class StockItem(MPTTModel):
help_text=_('Serial number for this item')
)
serial_int = models.IntegerField(default=0)
link = InvenTreeURLField(
verbose_name=_('External Link'),
max_length=125, blank=True,

View File

@ -560,9 +560,8 @@ class StockItemInstall(AjaxUpdateView):
# Filter for parts to install in this item
if self.install_item:
# Get parts used in this part's BOM
bom_items = self.part.get_bom_items()
allowed_parts = [item.sub_part for item in bom_items]
# Get all parts which can be installed into this part
allowed_parts = self.part.get_installed_part_options()
# Filter
items = items.filter(part__in=allowed_parts)

View File

@ -1128,7 +1128,9 @@ function loadStockTable(table, options) {
col = {
field: 'quantity',
sortName: 'stock',
title: '{% trans "Stock" %}',
sortable: true,
formatter: function(value, row) {
var val = parseFloat(value);