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
commit f9c004bd36
No known key found for this signature in database
GPG Key ID: F50EF5741D33E076
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 django.utils.translation import gettext as _
from InvenTree.helpers import DownloadFile, GetExportFormats from InvenTree.helpers import DownloadFile, GetExportFormats, normalize
from .admin import BomItemResource from .admin import BomItemResource
from .models import BomItem from .models import BomItem
@ -59,7 +59,7 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=Fa
uids = [] uids = []
def add_items(items, level): def add_items(items, level, cascade):
# Add items at a given layer # Add items at a given layer
for item in items: 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) 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: if max_levels is None or level < max_levels:
add_items(item.sub_part.bom_items.all().order_by('id'), level + 1) add_items(item.sub_part.bom_items.all().order_by('id'), level + 1)
if cascade: top_level_items = part.get_bom_items().order_by('id')
# Cascading (multi-level) BOM
# Start with the top level add_items(top_level_items, 1, cascade)
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')]
dataset = BomItemResource().export(queryset=bom_items, cascade=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('') stock_data.append('')
except AttributeError: except AttributeError:
stock_data.append('') stock_data.append('')
# Get part current stock # 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): for s_idx, header in enumerate(stock_headers):
try: try:

View File

@ -1392,6 +1392,27 @@ class Part(MPTTModel):
return BomItem.objects.filter(self.get_bom_item_filter(include_inherited=include_inherited)) 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): def get_used_in_filter(self, include_inherited=True):
""" """
Return a query filter for all parts that this part is used in. Return a query filter for all parts that this part is used in.

View File

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

View File

@ -876,6 +876,7 @@ class StockList(generics.ListCreateAPIView):
ordering_field_aliases = { ordering_field_aliases = {
'SKU': 'supplier_part__SKU', 'SKU': 'supplier_part__SKU',
'stock': ['quantity', 'serial_int', 'serial'],
} }
ordering_fields = [ ordering_fields = [
@ -887,6 +888,7 @@ class StockList(generics.ListCreateAPIView):
'stocktake_date', 'stocktake_date',
'expiry_date', 'expiry_date',
'quantity', 'quantity',
'stock',
'status', 'status',
'SKU', '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 from __future__ import unicode_literals
import os import os
import re
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError, FieldError from django.core.exceptions import ValidationError, FieldError
@ -223,6 +224,32 @@ class StockItem(MPTTModel):
self.scheduled_for_deletion = True self.scheduled_for_deletion = True
self.save() 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): def save(self, *args, **kwargs):
""" """
Save this StockItem to the database. Performs a number of checks: Save this StockItem to the database. Performs a number of checks:
@ -234,6 +261,8 @@ class StockItem(MPTTModel):
self.validate_unique() self.validate_unique()
self.clean() self.clean()
self.update_serial_number()
user = kwargs.pop('user', None) user = kwargs.pop('user', None)
# If 'add_note = False' specified, then no tracking note will be added for item creation # 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') help_text=_('Serial number for this item')
) )
serial_int = models.IntegerField(default=0)
link = InvenTreeURLField( link = InvenTreeURLField(
verbose_name=_('External Link'), verbose_name=_('External Link'),
max_length=125, blank=True, max_length=125, blank=True,

View File

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

View File

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

View File

@ -9,7 +9,6 @@
![SQLite](https://github.com/inventree/inventree/actions/workflows/coverage.yaml/badge.svg) ![SQLite](https://github.com/inventree/inventree/actions/workflows/coverage.yaml/badge.svg)
![MySQL](https://github.com/inventree/inventree/actions/workflows/mysql.yaml/badge.svg) ![MySQL](https://github.com/inventree/inventree/actions/workflows/mysql.yaml/badge.svg)
![PostgreSQL](https://github.com/inventree/inventree/actions/workflows/postgresql.yaml/badge.svg) ![PostgreSQL](https://github.com/inventree/inventree/actions/workflows/postgresql.yaml/badge.svg)
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/InvenTree/InvenTree)
InvenTree is an open-source Inventory Management System which provides powerful low-level stock control and part tracking. The core of the InvenTree system is a Python/Django database backend which provides an admin interface (web-based) and a JSON API for interaction with external interfaces and applications. InvenTree is an open-source Inventory Management System which provides powerful low-level stock control and part tracking. The core of the InvenTree system is a Python/Django database backend which provides an admin interface (web-based) and a JSON API for interaction with external interfaces and applications.
@ -17,6 +16,10 @@ InvenTree is designed to be lightweight and easy to use for SME or hobbyist appl
However, powerful business logic works in the background to ensure that stock tracking history is maintained, and users have ready access to stock level information. However, powerful business logic works in the background to ensure that stock tracking history is maintained, and users have ready access to stock level information.
# Demo
A demo instance of InvenTree is provided to allow users to explore the functionality of the software. [Read more here](https://inventree.readthedocs.io/en/latest/demo/)
# Docker # Docker
[![Docker Pulls](https://img.shields.io/docker/pulls/inventree/inventree)](https://hub.docker.com/r/inventree/inventree) [![Docker Pulls](https://img.shields.io/docker/pulls/inventree/inventree)](https://hub.docker.com/r/inventree/inventree)