mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-03 22:55:43 +00:00 
			
		
		
		
	Merge branch 'master' of https://github.com/inventree/InvenTree into workflow-remaster
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -78,5 +78,4 @@ locale_stats.json
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# node.js
 | 
					# node.js
 | 
				
			||||||
package-lock.json
 | 
					package-lock.json
 | 
				
			||||||
package.json
 | 
					 | 
				
			||||||
node_modules/
 | 
					node_modules/
 | 
				
			||||||
@@ -49,6 +49,9 @@ class ReferenceIndexingMixin(models.Model):
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    A mixin for keeping track of numerical copies of the "reference" field.
 | 
					    A mixin for keeping track of numerical copies of the "reference" field.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    !!DANGER!! always add `ReferenceIndexingSerializerMixin`to all your models serializers to
 | 
				
			||||||
 | 
					    ensure the reference field is not too big
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Here, we attempt to convert a "reference" field value (char) to an integer,
 | 
					    Here, we attempt to convert a "reference" field value (char) to an integer,
 | 
				
			||||||
    for performing fast natural sorting.
 | 
					    for performing fast natural sorting.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -69,6 +72,12 @@ class ReferenceIndexingMixin(models.Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        reference = getattr(self, 'reference', '')
 | 
					        reference = getattr(self, 'reference', '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.reference_int = extract_int(reference)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    reference_int = models.BigIntegerField(default=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def extract_int(reference):
 | 
				
			||||||
    # Default value if we cannot convert to an integer
 | 
					    # Default value if we cannot convert to an integer
 | 
				
			||||||
    ref_int = 0
 | 
					    ref_int = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -81,10 +90,7 @@ class ReferenceIndexingMixin(models.Model):
 | 
				
			|||||||
            ref_int = int(ref)
 | 
					            ref_int = int(ref)
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            ref_int = 0
 | 
					            ref_int = 0
 | 
				
			||||||
 | 
					    return ref_int
 | 
				
			||||||
        self.reference_int = ref_int
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    reference_int = models.IntegerField(default=0)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class InvenTreeAttachment(models.Model):
 | 
					class InvenTreeAttachment(models.Model):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ from django.conf import settings
 | 
				
			|||||||
from django.contrib.auth.models import User
 | 
					from django.contrib.auth.models import User
 | 
				
			||||||
from django.core.exceptions import ValidationError as DjangoValidationError
 | 
					from django.core.exceptions import ValidationError as DjangoValidationError
 | 
				
			||||||
from django.utils.translation import ugettext_lazy as _
 | 
					from django.utils.translation import ugettext_lazy as _
 | 
				
			||||||
 | 
					from django.db import models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from djmoney.contrib.django_rest_framework.fields import MoneyField
 | 
					from djmoney.contrib.django_rest_framework.fields import MoneyField
 | 
				
			||||||
from djmoney.money import Money
 | 
					from djmoney.money import Money
 | 
				
			||||||
@@ -27,6 +28,8 @@ from rest_framework.fields import empty
 | 
				
			|||||||
from rest_framework.exceptions import ValidationError
 | 
					from rest_framework.exceptions import ValidationError
 | 
				
			||||||
from rest_framework.serializers import DecimalField
 | 
					from rest_framework.serializers import DecimalField
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .models import extract_int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class InvenTreeMoneySerializer(MoneyField):
 | 
					class InvenTreeMoneySerializer(MoneyField):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
@@ -239,6 +242,17 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
 | 
				
			|||||||
        return data
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ReferenceIndexingSerializerMixin():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This serializer mixin ensures the the reference is not to big / small
 | 
				
			||||||
 | 
					    for the BigIntegerField
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def validate_reference(self, value):
 | 
				
			||||||
 | 
					        if extract_int(value) > models.BigIntegerField.MAX_BIGINT:
 | 
				
			||||||
 | 
					            raise serializers.ValidationError('reference is to to big')
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class InvenTreeAttachmentSerializerField(serializers.FileField):
 | 
					class InvenTreeAttachmentSerializerField(serializers.FileField):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Override the DRF native FileField serializer,
 | 
					    Override the DRF native FileField serializer,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -781,6 +781,7 @@ input[type="submit"] {
 | 
				
			|||||||
.btn-small {
 | 
					.btn-small {
 | 
				
			||||||
    padding: 3px;
 | 
					    padding: 3px;
 | 
				
			||||||
    padding-left: 5px;
 | 
					    padding-left: 5px;
 | 
				
			||||||
 | 
					    padding-right: 5px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.btn-remove {
 | 
					.btn-remove {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,11 +12,15 @@ import common.models
 | 
				
			|||||||
INVENTREE_SW_VERSION = "0.6.0 dev"
 | 
					INVENTREE_SW_VERSION = "0.6.0 dev"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# InvenTree API version
 | 
					# InvenTree API version
 | 
				
			||||||
INVENTREE_API_VERSION = 18
 | 
					INVENTREE_API_VERSION = 19
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
Increment this API version number whenever there is a significant change to the API that any clients need to know about
 | 
					Increment this API version number whenever there is a significant change to the API that any clients need to know about
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					v19 -> 2021-12-02
 | 
				
			||||||
 | 
					    - Adds the ability to filter the StockItem API by "part_tree"
 | 
				
			||||||
 | 
					    - Returns only stock items which match a particular part.tree_id field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
v18 -> 2021-11-15
 | 
					v18 -> 2021-11-15
 | 
				
			||||||
    - Adds the ability to filter BomItem API by "uses" field
 | 
					    - Adds the ability to filter BomItem API by "uses" field
 | 
				
			||||||
    - This returns a list of all BomItems which "use" the specified part
 | 
					    - This returns a list of all BomItems which "use" the specified part
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										18
									
								
								InvenTree/build/migrations/0034_alter_build_reference_int.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								InvenTree/build/migrations/0034_alter_build_reference_int.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					# Generated by Django 3.2.5 on 2021-12-01 21:39
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('build', '0033_auto_20211128_0151'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='build',
 | 
				
			||||||
 | 
					            name='reference_int',
 | 
				
			||||||
 | 
					            field=models.BigIntegerField(default=0),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -16,7 +16,7 @@ from rest_framework import serializers
 | 
				
			|||||||
from rest_framework.serializers import ValidationError
 | 
					from rest_framework.serializers import ValidationError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentSerializer
 | 
					from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentSerializer
 | 
				
			||||||
from InvenTree.serializers import UserSerializerBrief
 | 
					from InvenTree.serializers import UserSerializerBrief, ReferenceIndexingSerializerMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import InvenTree.helpers
 | 
					import InvenTree.helpers
 | 
				
			||||||
from InvenTree.serializers import InvenTreeDecimalField
 | 
					from InvenTree.serializers import InvenTreeDecimalField
 | 
				
			||||||
@@ -32,7 +32,7 @@ from users.serializers import OwnerSerializer
 | 
				
			|||||||
from .models import Build, BuildItem, BuildOrderAttachment
 | 
					from .models import Build, BuildItem, BuildOrderAttachment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BuildSerializer(InvenTreeModelSerializer):
 | 
					class BuildSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Serializes a Build object
 | 
					    Serializes a Build object
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										23
									
								
								InvenTree/order/migrations/0054_auto_20211201_2139.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								InvenTree/order/migrations/0054_auto_20211201_2139.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					# Generated by Django 3.2.5 on 2021-12-01 21:39
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('order', '0053_auto_20211128_0151'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='purchaseorder',
 | 
				
			||||||
 | 
					            name='reference_int',
 | 
				
			||||||
 | 
					            field=models.BigIntegerField(default=0),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='salesorder',
 | 
				
			||||||
 | 
					            name='reference_int',
 | 
				
			||||||
 | 
					            field=models.BigIntegerField(default=0),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -24,6 +24,7 @@ from InvenTree.serializers import InvenTreeAttachmentSerializer
 | 
				
			|||||||
from InvenTree.serializers import InvenTreeModelSerializer
 | 
					from InvenTree.serializers import InvenTreeModelSerializer
 | 
				
			||||||
from InvenTree.serializers import InvenTreeDecimalField
 | 
					from InvenTree.serializers import InvenTreeDecimalField
 | 
				
			||||||
from InvenTree.serializers import InvenTreeMoneySerializer
 | 
					from InvenTree.serializers import InvenTreeMoneySerializer
 | 
				
			||||||
 | 
					from InvenTree.serializers import ReferenceIndexingSerializerMixin
 | 
				
			||||||
from InvenTree.status_codes import StockStatus
 | 
					from InvenTree.status_codes import StockStatus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from part.serializers import PartBriefSerializer
 | 
					from part.serializers import PartBriefSerializer
 | 
				
			||||||
@@ -39,7 +40,7 @@ from .models import SalesOrderAllocation
 | 
				
			|||||||
from users.serializers import OwnerSerializer
 | 
					from users.serializers import OwnerSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class POSerializer(InvenTreeModelSerializer):
 | 
					class POSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer):
 | 
				
			||||||
    """ Serializer for a PurchaseOrder object """
 | 
					    """ Serializer for a PurchaseOrder object """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, *args, **kwargs):
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
@@ -394,7 +395,7 @@ class POAttachmentSerializer(InvenTreeAttachmentSerializer):
 | 
				
			|||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SalesOrderSerializer(InvenTreeModelSerializer):
 | 
					class SalesOrderSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Serializers for the SalesOrder object
 | 
					    Serializers for the SalesOrder object
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -105,6 +105,25 @@ class PurchaseOrderTest(OrderTest):
 | 
				
			|||||||
        self.assertEqual(data['pk'], 1)
 | 
					        self.assertEqual(data['pk'], 1)
 | 
				
			||||||
        self.assertEqual(data['description'], 'Ordering some screws')
 | 
					        self.assertEqual(data['description'], 'Ordering some screws')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_po_reference(self):
 | 
				
			||||||
 | 
					        """test that a reference with a too big / small reference is not possible"""
 | 
				
			||||||
 | 
					        # get permissions
 | 
				
			||||||
 | 
					        self.assignRole('purchase_order.add')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        url = reverse('api-po-list')
 | 
				
			||||||
 | 
					        huge_numer = 9223372036854775808
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # too big
 | 
				
			||||||
 | 
					        self.post(
 | 
				
			||||||
 | 
					            url,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                'supplier': 1,
 | 
				
			||||||
 | 
					                'reference': huge_numer,
 | 
				
			||||||
 | 
					                'description': 'PO not created via the API',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            expected_code=400
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_po_attachments(self):
 | 
					    def test_po_attachments(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        url = reverse('api-po-attachment-list')
 | 
					        url = reverse('api-po-attachment-list')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1075,6 +1075,7 @@ class PartList(generics.ListCreateAPIView):
 | 
				
			|||||||
        'revision',
 | 
					        'revision',
 | 
				
			||||||
        'keywords',
 | 
					        'keywords',
 | 
				
			||||||
        'category__name',
 | 
					        'category__name',
 | 
				
			||||||
 | 
					        'manufacturer_parts__MPN',
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -322,7 +322,14 @@
 | 
				
			|||||||
                <tr>
 | 
					                <tr>
 | 
				
			||||||
                    <td><span class='fas fa-hashtag'></span></td>
 | 
					                    <td><span class='fas fa-hashtag'></span></td>
 | 
				
			||||||
                    <td>{% trans "Latest Serial Number" %}</td>
 | 
					                    <td>{% trans "Latest Serial Number" %}</td>
 | 
				
			||||||
                    <td>{{ part.getLatestSerialNumber }}{% include "clip.html"%}</td>
 | 
					                    <td>
 | 
				
			||||||
 | 
					                        {{ part.getLatestSerialNumber }}
 | 
				
			||||||
 | 
					                        <div class='btn-group float-right' role='group'>
 | 
				
			||||||
 | 
					                            <a class='btn btn-small btn-outline-secondary text-sm' href='#' id='serial-number-search' title='{% trans "Search for serial number" %}'>
 | 
				
			||||||
 | 
					                                <span class='fas fa-search'></span>
 | 
				
			||||||
 | 
					                            </a>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
                </tr>
 | 
					                </tr>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
                {% if part.default_location %}
 | 
					                {% if part.default_location %}
 | 
				
			||||||
@@ -577,4 +584,8 @@
 | 
				
			|||||||
        $('#collapse-part-details').collapse('show');
 | 
					        $('#collapse-part-details').collapse('show');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('#serial-number-search').click(function() {
 | 
				
			||||||
 | 
					        findStockItemBySerialNumber({{ part.pk }});
 | 
				
			||||||
 | 
					    });    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
@@ -313,7 +313,7 @@ class StockFilter(rest_filters.FilterSet):
 | 
				
			|||||||
    # Serial number filtering
 | 
					    # Serial number filtering
 | 
				
			||||||
    serial_gte = rest_filters.NumberFilter(label='Serial number GTE', field_name='serial', lookup_expr='gte')
 | 
					    serial_gte = rest_filters.NumberFilter(label='Serial number GTE', field_name='serial', lookup_expr='gte')
 | 
				
			||||||
    serial_lte = rest_filters.NumberFilter(label='Serial number LTE', field_name='serial', lookup_expr='lte')
 | 
					    serial_lte = rest_filters.NumberFilter(label='Serial number LTE', field_name='serial', lookup_expr='lte')
 | 
				
			||||||
    serial = rest_filters.NumberFilter(label='Serial number', field_name='serial', lookup_expr='exact')
 | 
					    serial = rest_filters.CharFilter(label='Serial number', field_name='serial', lookup_expr='exact')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    serialized = rest_filters.BooleanFilter(label='Has serial number', method='filter_serialized')
 | 
					    serialized = rest_filters.BooleanFilter(label='Has serial number', method='filter_serialized')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -703,6 +703,18 @@ class StockList(generics.ListCreateAPIView):
 | 
				
			|||||||
            except (ValueError, StockItem.DoesNotExist):
 | 
					            except (ValueError, StockItem.DoesNotExist):
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Filter by "part tree" - only allow parts within a given variant tree
 | 
				
			||||||
 | 
					        part_tree = params.get('part_tree', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if part_tree is not None:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                part = Part.objects.get(pk=part_tree)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if part.tree_id is not None:
 | 
				
			||||||
 | 
					                    queryset = queryset.filter(part__tree_id=part.tree_id)
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Filter by 'allocated' parts?
 | 
					        # Filter by 'allocated' parts?
 | 
				
			||||||
        allocated = params.get('allocated', None)
 | 
					        allocated = params.get('allocated', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,6 @@ 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
 | 
				
			||||||
@@ -39,6 +38,7 @@ import label.models
 | 
				
			|||||||
from InvenTree.status_codes import StockStatus, StockHistoryCode
 | 
					from InvenTree.status_codes import StockStatus, StockHistoryCode
 | 
				
			||||||
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
 | 
					from InvenTree.models import InvenTreeTree, InvenTreeAttachment
 | 
				
			||||||
from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField
 | 
					from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField
 | 
				
			||||||
 | 
					from InvenTree.serializers import extract_int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from users.models import Owner
 | 
					from users.models import Owner
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -236,17 +236,7 @@ class StockItem(MPTTModel):
 | 
				
			|||||||
        serial_int = 0
 | 
					        serial_int = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if serial is not None:
 | 
					        if serial is not None:
 | 
				
			||||||
 | 
					            serial_int = extract_int(str(serial))
 | 
				
			||||||
            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
 | 
					        self.serial_int = serial_int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,7 +32,7 @@ from company.serializers import SupplierPartSerializer
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import InvenTree.helpers
 | 
					import InvenTree.helpers
 | 
				
			||||||
import InvenTree.serializers
 | 
					import InvenTree.serializers
 | 
				
			||||||
from InvenTree.serializers import InvenTreeDecimalField
 | 
					from InvenTree.serializers import InvenTreeDecimalField, extract_int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from part.serializers import PartBriefSerializer
 | 
					from part.serializers import PartBriefSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -73,6 +73,11 @@ class StockItemSerializerBrief(InvenTree.serializers.InvenTreeModelSerializer):
 | 
				
			|||||||
            'uid',
 | 
					            'uid',
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def validate_serial(self, value):
 | 
				
			||||||
 | 
					        if extract_int(value) > 2147483647:
 | 
				
			||||||
 | 
					            raise serializers.ValidationError('serial is to to big')
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
 | 
					class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
 | 
				
			||||||
    """ Serializer for a StockItem:
 | 
					    """ Serializer for a StockItem:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -148,17 +148,24 @@
 | 
				
			|||||||
        <td><span class='fas fa-hashtag'></span></td>
 | 
					        <td><span class='fas fa-hashtag'></span></td>
 | 
				
			||||||
        <td>{% trans "Serial Number" %}</td>
 | 
					        <td>{% trans "Serial Number" %}</td>
 | 
				
			||||||
        <td>
 | 
					        <td>
 | 
				
			||||||
        {% if previous %}
 | 
					 | 
				
			||||||
            <a class="btn btn-outline-secondary" aria-label="{% trans 'previous page' %}" href="{% url request.resolver_match.url_name previous.id %}">
 | 
					 | 
				
			||||||
                <small>{{ previous.serial }}</small>  ‹
 | 
					 | 
				
			||||||
            </a>
 | 
					 | 
				
			||||||
        {% endif %}
 | 
					 | 
				
			||||||
            {{ item.serial }}
 | 
					            {{ item.serial }}
 | 
				
			||||||
        {% if next %}
 | 
					            <div class='btn-group float-right' role='group'>
 | 
				
			||||||
            <a class="btn btn-outline-secondary text-sm" aria-label="{% trans 'next page' %}" href="{% url request.resolver_match.url_name next.id %}">
 | 
					                {% if previous %}
 | 
				
			||||||
                ›  <small>{{ next.serial }}</small>
 | 
					                <a class="btn btn-small btn-outline-secondary" aria-label="{% trans 'previous page' %}" href="{% url request.resolver_match.url_name previous.id %}" title='{% trans "Navigate to previous serial number" %}'>
 | 
				
			||||||
 | 
					                    <span class='fas fa-angle-left'></span>
 | 
				
			||||||
 | 
					                    <small>{{ previous.serial }}</small>
 | 
				
			||||||
                </a>
 | 
					                </a>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					                <a class='btn btn-small btn-outline-secondary text-sm' href='#' id='serial-number-search' title='{% trans "Search for serial number" %}'>
 | 
				
			||||||
 | 
					                    <span class='fas fa-search'></span>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					                {% if next %}
 | 
				
			||||||
 | 
					                <a class="btn btn-small btn-outline-secondary text-sm" aria-label="{% trans 'next page' %}" href="{% url request.resolver_match.url_name next.id %}" title='{% trans "Navigate to next serial number" %}'>
 | 
				
			||||||
 | 
					                    <small>{{ next.serial }}</small>
 | 
				
			||||||
 | 
					                    <span class='fas fa-angle-right'></span>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
        </td>
 | 
					        </td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
    {% else %}
 | 
					    {% else %}
 | 
				
			||||||
@@ -592,4 +599,8 @@ $("#stock-return-from-customer").click(function() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
{% endif %}
 | 
					{% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$('#serial-number-search').click(function() {
 | 
				
			||||||
 | 
					    findStockItemBySerialNumber({{ item.part.pk }});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -121,7 +121,6 @@
 | 
				
			|||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    {% include 'modals.html' %}
 | 
					    {% include 'modals.html' %}
 | 
				
			||||||
    {% include 'about.html' %}
 | 
					    {% include 'about.html' %}
 | 
				
			||||||
    {% include "notifications.html" %}
 | 
					 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<!-- Scripts -->
 | 
					<!-- Scripts -->
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,6 +44,7 @@
 | 
				
			|||||||
    editStockItem,
 | 
					    editStockItem,
 | 
				
			||||||
    editStockLocation,
 | 
					    editStockLocation,
 | 
				
			||||||
    exportStock,
 | 
					    exportStock,
 | 
				
			||||||
 | 
					    findStockItemBySerialNumber,
 | 
				
			||||||
    loadInstalledInTable,
 | 
					    loadInstalledInTable,
 | 
				
			||||||
    loadStockLocationTable,
 | 
					    loadStockLocationTable,
 | 
				
			||||||
    loadStockTable,
 | 
					    loadStockTable,
 | 
				
			||||||
@@ -394,6 +395,87 @@ function createNewStockItem(options={}) {
 | 
				
			|||||||
    constructForm(url, options);
 | 
					    constructForm(url, options);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Launch a modal form to find a particular stock item by serial number.
 | 
				
			||||||
 | 
					 * Arguments:
 | 
				
			||||||
 | 
					 * - part: ID (PK) of the part in question
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function findStockItemBySerialNumber(part_id) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructFormBody({}, {
 | 
				
			||||||
 | 
					        title: '{% trans "Find Serial Number" %}',
 | 
				
			||||||
 | 
					        fields: {
 | 
				
			||||||
 | 
					            serial: {
 | 
				
			||||||
 | 
					                label: '{% trans "Serial Number" %}',
 | 
				
			||||||
 | 
					                help_text: '{% trans "Enter serial number" %}',
 | 
				
			||||||
 | 
					                placeholder: '{% trans "Enter serial number" %}',
 | 
				
			||||||
 | 
					                required: true,
 | 
				
			||||||
 | 
					                type: 'string',
 | 
				
			||||||
 | 
					                value: '',
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        onSubmit: function(fields, opts) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var serial = getFormFieldValue('serial', fields['serial'], opts);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					            serial = serial.toString().trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!serial) {
 | 
				
			||||||
 | 
					                handleFormErrors(
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        'serial': [
 | 
				
			||||||
 | 
					                            '{% trans "Enter a serial number" %}',
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    }, fields, opts
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            inventreeGet(
 | 
				
			||||||
 | 
					                '{% url "api-stock-list" %}',
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    part_tree: part_id,
 | 
				
			||||||
 | 
					                    serial: serial,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    success: function(response) {
 | 
				
			||||||
 | 
					                        if (response.length == 0) {
 | 
				
			||||||
 | 
					                            // No results!
 | 
				
			||||||
 | 
					                            handleFormErrors(
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    'serial': [
 | 
				
			||||||
 | 
					                                        '{% trans "No matching serial number" %}',
 | 
				
			||||||
 | 
					                                    ]
 | 
				
			||||||
 | 
					                                }, fields, opts
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                        } else if (response.length > 1) {
 | 
				
			||||||
 | 
					                            // Too many results!
 | 
				
			||||||
 | 
					                            handleFormErrors(
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    'serial': [
 | 
				
			||||||
 | 
					                                        '{% trans "More than one matching result found" %}',
 | 
				
			||||||
 | 
					                                    ]
 | 
				
			||||||
 | 
					                                }, fields, opts
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            $(opts.modal).modal('hide');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            // Redirect
 | 
				
			||||||
 | 
					                            var pk = response[0].pk;
 | 
				
			||||||
 | 
					                            location.href = `/stock/item/${pk}/`;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    error: function(xhr) {
 | 
				
			||||||
 | 
					                        showApiError(xhr, opts.url);
 | 
				
			||||||
 | 
					                        $(opts.modal).modal('hide');
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Stock API functions
 | 
					/* Stock API functions
 | 
				
			||||||
 * Requires api.js to be loaded first
 | 
					 * Requires api.js to be loaded first
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "eslint": "^8.3.0",
 | 
				
			||||||
 | 
					    "eslint-config-google": "^0.14.0",
 | 
				
			||||||
 | 
					    "markuplint": "^1.11.4"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user