mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	Adds API endpoint for serialization of stock items
This commit is contained in:
		@@ -101,6 +101,27 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
 | 
				
			|||||||
        instance.mark_for_deletion()
 | 
					        instance.mark_for_deletion()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StockItemSerialize(generics.CreateAPIView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    API endpoint for serializing a stock item
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    queryset = StockItem.objects.none()
 | 
				
			||||||
 | 
					    serializer_class = StockSerializers.SerializeStockItemSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_serializer_context(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        context = super().get_serializer_context()
 | 
				
			||||||
 | 
					        context['request'] = self.request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            context['item'] = StockItem.objects.get(pk=self.kwargs.get('pk', None))
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StockAdjustView(generics.CreateAPIView):
 | 
					class StockAdjustView(generics.CreateAPIView):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    A generic class for handling stocktake actions.
 | 
					    A generic class for handling stocktake actions.
 | 
				
			||||||
@@ -1126,8 +1147,11 @@ stock_api_urls = [
 | 
				
			|||||||
        url(r'^.*$', StockTrackingList.as_view(), name='api-stock-tracking-list'),
 | 
					        url(r'^.*$', StockTrackingList.as_view(), name='api-stock-tracking-list'),
 | 
				
			||||||
    ])),
 | 
					    ])),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Detail for a single stock item
 | 
					    # Detail views for a single stock item
 | 
				
			||||||
    url(r'^(?P<pk>\d+)/', StockDetail.as_view(), name='api-stock-detail'),
 | 
					    url(r'^(?P<pk>\d+)/', include([
 | 
				
			||||||
 | 
					        url(r'^serialize/', StockItemSerialize.as_view(), name='api-stock-item-serialize'),
 | 
				
			||||||
 | 
					        url(r'^.*$', StockDetail.as_view(), name='api-stock-detail'),
 | 
				
			||||||
 | 
					    ])),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Anything else
 | 
					    # Anything else
 | 
				
			||||||
    url(r'^.*$', StockList.as_view(), name='api-stock-list'),
 | 
					    url(r'^.*$', StockList.as_view(), name='api-stock-list'),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ from decimal import Decimal
 | 
				
			|||||||
from datetime import datetime, timedelta
 | 
					from datetime import datetime, timedelta
 | 
				
			||||||
from django.db import transaction
 | 
					from django.db import transaction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.models.functions import Coalesce
 | 
					from django.db.models.functions import Coalesce
 | 
				
			||||||
from django.db.models import Case, When, Value
 | 
					from django.db.models import Case, When, Value
 | 
				
			||||||
@@ -27,14 +28,15 @@ from .models import StockItemTestResult
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import common.models
 | 
					import common.models
 | 
				
			||||||
from common.settings import currency_code_default, currency_code_mappings
 | 
					from common.settings import currency_code_default, currency_code_mappings
 | 
				
			||||||
 | 
					 | 
				
			||||||
from company.serializers import SupplierPartSerializer
 | 
					from company.serializers import SupplierPartSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import InvenTree.helpers
 | 
				
			||||||
 | 
					import InvenTree.serializers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from part.serializers import PartBriefSerializer
 | 
					from part.serializers import PartBriefSerializer
 | 
				
			||||||
from InvenTree.serializers import UserSerializerBrief, InvenTreeModelSerializer, InvenTreeMoneySerializer
 | 
					 | 
				
			||||||
from InvenTree.serializers import InvenTreeAttachmentSerializer, InvenTreeAttachmentSerializerField
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LocationBriefSerializer(InvenTreeModelSerializer):
 | 
					class LocationBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Provides a brief serializer for a StockLocation object
 | 
					    Provides a brief serializer for a StockLocation object
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
@@ -48,7 +50,7 @@ class LocationBriefSerializer(InvenTreeModelSerializer):
 | 
				
			|||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StockItemSerializerBrief(InvenTreeModelSerializer):
 | 
					class StockItemSerializerBrief(InvenTree.serializers.InvenTreeModelSerializer):
 | 
				
			||||||
    """ Brief serializers for a StockItem """
 | 
					    """ Brief serializers for a StockItem """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    location_name = serializers.CharField(source='location', read_only=True)
 | 
					    location_name = serializers.CharField(source='location', read_only=True)
 | 
				
			||||||
@@ -70,7 +72,7 @@ class StockItemSerializerBrief(InvenTreeModelSerializer):
 | 
				
			|||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StockItemSerializer(InvenTreeModelSerializer):
 | 
					class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
 | 
				
			||||||
    """ Serializer for a StockItem:
 | 
					    """ Serializer for a StockItem:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - Includes serialization for the linked part
 | 
					    - Includes serialization for the linked part
 | 
				
			||||||
@@ -146,7 +148,7 @@ class StockItemSerializer(InvenTreeModelSerializer):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    required_tests = serializers.IntegerField(source='required_test_count', read_only=True, required=False)
 | 
					    required_tests = serializers.IntegerField(source='required_test_count', read_only=True, required=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    purchase_price = InvenTreeMoneySerializer(
 | 
					    purchase_price = InvenTree.serializers.InvenTreeMoneySerializer(
 | 
				
			||||||
        label=_('Purchase Price'),
 | 
					        label=_('Purchase Price'),
 | 
				
			||||||
        max_digits=19, decimal_places=4,
 | 
					        max_digits=19, decimal_places=4,
 | 
				
			||||||
        allow_null=True,
 | 
					        allow_null=True,
 | 
				
			||||||
@@ -247,14 +249,127 @@ class StockItemSerializer(InvenTreeModelSerializer):
 | 
				
			|||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StockQuantitySerializer(InvenTreeModelSerializer):
 | 
					class SerializeStockItemSerializer(serializers.Serializer):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A DRF serializer for "serializing" a StockItem.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (Sorry for the confusing naming...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Here, "serializing" means splitting out a single StockItem,
 | 
				
			||||||
 | 
					    into multiple single-quantity items with an assigned serial number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Note: The base StockItem object is provided to the serializer context
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        model = StockItem
 | 
					        fields = [
 | 
				
			||||||
        fields = ('quantity',)
 | 
					            'quantity',
 | 
				
			||||||
 | 
					            'serial_numbers',
 | 
				
			||||||
 | 
					            'destination',
 | 
				
			||||||
 | 
					            'notes',
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    quantity = serializers.IntegerField(
 | 
				
			||||||
 | 
					        min_value=0,
 | 
				
			||||||
 | 
					        required=True,
 | 
				
			||||||
 | 
					        label=_('Quantity'),
 | 
				
			||||||
 | 
					        help_text=_('Enter number of stock items to serialize'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def validate_quantity(self, quantity):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        item = self.context['item']        
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if quantity < 0:
 | 
				
			||||||
 | 
					            raise ValidationError(_("Quantity must be greater than zero"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if quantity > item.quantity:
 | 
				
			||||||
 | 
					            q = item.quantity
 | 
				
			||||||
 | 
					            raise ValidationError(_(f"Quantity must not exceed available stock quantity ({q})"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return quantity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    serial_numbers = serializers.CharField(
 | 
				
			||||||
 | 
					        label=_('Serial Numbers'),
 | 
				
			||||||
 | 
					        help_text=_('Enter serial numbers for new items'),
 | 
				
			||||||
 | 
					        allow_blank=False,
 | 
				
			||||||
 | 
					        required=True,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    destination = serializers.PrimaryKeyRelatedField(
 | 
				
			||||||
 | 
					        queryset=StockLocation.objects.all(),
 | 
				
			||||||
 | 
					        many=False,
 | 
				
			||||||
 | 
					        required=True,
 | 
				
			||||||
 | 
					        allow_null=False,
 | 
				
			||||||
 | 
					        label=_('Location'),
 | 
				
			||||||
 | 
					        help_text=_('Destination stock location'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    notes = serializers.CharField(
 | 
				
			||||||
 | 
					        required=False,
 | 
				
			||||||
 | 
					        allow_blank=True,
 | 
				
			||||||
 | 
					        label=_("Notes"),
 | 
				
			||||||
 | 
					        help_text=_("Optional note field")
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def validate(self, data):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Check that the supplied serial numbers are valid
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data = super().validate(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        item = self.context['item']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not item.part.trackable:
 | 
				
			||||||
 | 
					            raise ValidationError(_("Serial numbers cannot be assigned to this part"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Ensure the serial numbers are valid!
 | 
				
			||||||
 | 
					        quantity = data['quantity']
 | 
				
			||||||
 | 
					        serial_numbers = data['serial_numbers']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            serials = InvenTree.helpers.extract_serial_numbers(serial_numbers, quantity)
 | 
				
			||||||
 | 
					        except DjangoValidationError as e:
 | 
				
			||||||
 | 
					            raise ValidationError({
 | 
				
			||||||
 | 
					                'serial_numbers': e.messages,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        existing = item.part.find_conflicting_serial_numbers(serials)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if len(existing) > 0:
 | 
				
			||||||
 | 
					            exists = ','.join([str(x) for x in existing])
 | 
				
			||||||
 | 
					            error = _('Serial numbers already exist') + ": " + exists
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            raise ValidationError({
 | 
				
			||||||
 | 
					                'serial_numbers': error,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def save(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        item = self.context['item']
 | 
				
			||||||
 | 
					        request = self.context['request']
 | 
				
			||||||
 | 
					        user = request.user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data = self.validated_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        serials = InvenTree.helpers.extract_serial_numbers(
 | 
				
			||||||
 | 
					            data['serial_numbers'],
 | 
				
			||||||
 | 
					            data['quantity'],
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        item.serializeStock(
 | 
				
			||||||
 | 
					            data['quantity'],
 | 
				
			||||||
 | 
					            serials,
 | 
				
			||||||
 | 
					            user,
 | 
				
			||||||
 | 
					            notes=data.get('notes', ''),
 | 
				
			||||||
 | 
					            location=data['destination'],
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LocationSerializer(InvenTreeModelSerializer):
 | 
					class LocationSerializer(InvenTree.serializers.InvenTreeModelSerializer):
 | 
				
			||||||
    """ Detailed information about a stock location
 | 
					    """ Detailed information about a stock location
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -278,7 +393,7 @@ class LocationSerializer(InvenTreeModelSerializer):
 | 
				
			|||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StockItemAttachmentSerializer(InvenTreeAttachmentSerializer):
 | 
					class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSerializer):
 | 
				
			||||||
    """ Serializer for StockItemAttachment model """
 | 
					    """ Serializer for StockItemAttachment model """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, *args, **kwargs):
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
@@ -289,9 +404,9 @@ class StockItemAttachmentSerializer(InvenTreeAttachmentSerializer):
 | 
				
			|||||||
        if user_detail is not True:
 | 
					        if user_detail is not True:
 | 
				
			||||||
            self.fields.pop('user_detail')
 | 
					            self.fields.pop('user_detail')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    user_detail = UserSerializerBrief(source='user', read_only=True)
 | 
					    user_detail = InvenTree.serializers.UserSerializerBrief(source='user', read_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    attachment = InvenTreeAttachmentSerializerField(required=True)
 | 
					    attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(required=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # TODO: Record the uploading user when creating or updating an attachment!
 | 
					    # TODO: Record the uploading user when creating or updating an attachment!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -316,14 +431,14 @@ class StockItemAttachmentSerializer(InvenTreeAttachmentSerializer):
 | 
				
			|||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StockItemTestResultSerializer(InvenTreeModelSerializer):
 | 
					class StockItemTestResultSerializer(InvenTree.serializers.InvenTreeModelSerializer):
 | 
				
			||||||
    """ Serializer for the StockItemTestResult model """
 | 
					    """ Serializer for the StockItemTestResult model """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    user_detail = UserSerializerBrief(source='user', read_only=True)
 | 
					    user_detail = InvenTree.serializers.UserSerializerBrief(source='user', read_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    key = serializers.CharField(read_only=True)
 | 
					    key = serializers.CharField(read_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    attachment = InvenTreeAttachmentSerializerField(required=False)
 | 
					    attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(required=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, *args, **kwargs):
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
        user_detail = kwargs.pop('user_detail', False)
 | 
					        user_detail = kwargs.pop('user_detail', False)
 | 
				
			||||||
@@ -357,7 +472,7 @@ class StockItemTestResultSerializer(InvenTreeModelSerializer):
 | 
				
			|||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StockTrackingSerializer(InvenTreeModelSerializer):
 | 
					class StockTrackingSerializer(InvenTree.serializers.InvenTreeModelSerializer):
 | 
				
			||||||
    """ Serializer for StockItemTracking model """
 | 
					    """ Serializer for StockItemTracking model """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, *args, **kwargs):
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
@@ -377,7 +492,7 @@ class StockTrackingSerializer(InvenTreeModelSerializer):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    item_detail = StockItemSerializerBrief(source='item', many=False, read_only=True)
 | 
					    item_detail = StockItemSerializerBrief(source='item', many=False, read_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    user_detail = UserSerializerBrief(source='user', many=False, read_only=True)
 | 
					    user_detail = InvenTree.serializers.UserSerializerBrief(source='user', many=False, read_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    deltas = serializers.JSONField(read_only=True)
 | 
					    deltas = serializers.JSONField(read_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -418,12 +418,18 @@
 | 
				
			|||||||
{{ block.super }}
 | 
					{{ block.super }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$("#stock-serialize").click(function() {
 | 
					$("#stock-serialize").click(function() {
 | 
				
			||||||
    launchModalForm(
 | 
					
 | 
				
			||||||
        "{% url 'stock-item-serialize' item.id %}",
 | 
					    serializeStockItem({{ item.pk }}, {
 | 
				
			||||||
        {
 | 
					        reload: true,
 | 
				
			||||||
            reload: true,
 | 
					        data: {
 | 
				
			||||||
 | 
					            quantity: {{ item.quantity }},
 | 
				
			||||||
 | 
					            {% if item.location %}
 | 
				
			||||||
 | 
					            destination: {{ item.location.pk }},
 | 
				
			||||||
 | 
					            {% elif item.part.default_location %}
 | 
				
			||||||
 | 
					            destination: {{ item.part.default_location.pk }},
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    );
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$('#stock-install-in').click(function() {
 | 
					$('#stock-install-in').click(function() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,46 +126,6 @@ class StockItemTest(StockViewTestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.assertIn(expected, str(response.content))
 | 
					        self.assertIn(expected, str(response.content))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_serialize_item(self):
 | 
					 | 
				
			||||||
        # Test the serialization view
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        url = reverse('stock-item-serialize', args=(100,))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # GET the form
 | 
					 | 
				
			||||||
        response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
 | 
					 | 
				
			||||||
        self.assertEqual(response.status_code, 200)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        data_valid = {
 | 
					 | 
				
			||||||
            'quantity': 5,
 | 
					 | 
				
			||||||
            'serial_numbers': '1-5',
 | 
					 | 
				
			||||||
            'destination': 4,
 | 
					 | 
				
			||||||
            'notes': 'Serializing stock test'
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        data_invalid = {
 | 
					 | 
				
			||||||
            'quantity': 4,
 | 
					 | 
				
			||||||
            'serial_numbers': 'dd-23-adf',
 | 
					 | 
				
			||||||
            'destination': 'blorg'
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # POST
 | 
					 | 
				
			||||||
        response = self.client.post(url, data_valid, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
 | 
					 | 
				
			||||||
        self.assertEqual(response.status_code, 200)
 | 
					 | 
				
			||||||
        data = json.loads(response.content)
 | 
					 | 
				
			||||||
        self.assertTrue(data['form_valid'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Try again to serialize with the same numbers
 | 
					 | 
				
			||||||
        response = self.client.post(url, data_valid, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
 | 
					 | 
				
			||||||
        self.assertEqual(response.status_code, 200)
 | 
					 | 
				
			||||||
        data = json.loads(response.content)
 | 
					 | 
				
			||||||
        self.assertFalse(data['form_valid'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # POST with invalid data
 | 
					 | 
				
			||||||
        response = self.client.post(url, data_invalid, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
 | 
					 | 
				
			||||||
        self.assertEqual(response.status_code, 200)
 | 
					 | 
				
			||||||
        data = json.loads(response.content)
 | 
					 | 
				
			||||||
        self.assertFalse(data['form_valid'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StockOwnershipTest(StockViewTestCase):
 | 
					class StockOwnershipTest(StockViewTestCase):
 | 
				
			||||||
    """ Tests for stock ownership views """
 | 
					    """ Tests for stock ownership views """
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,6 @@ location_urls = [
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
stock_item_detail_urls = [
 | 
					stock_item_detail_urls = [
 | 
				
			||||||
    url(r'^convert/', views.StockItemConvert.as_view(), name='stock-item-convert'),
 | 
					    url(r'^convert/', views.StockItemConvert.as_view(), name='stock-item-convert'),
 | 
				
			||||||
    url(r'^serialize/', views.StockItemSerialize.as_view(), name='stock-item-serialize'),
 | 
					 | 
				
			||||||
    url(r'^delete/', views.StockItemDelete.as_view(), name='stock-item-delete'),
 | 
					    url(r'^delete/', views.StockItemDelete.as_view(), name='stock-item-delete'),
 | 
				
			||||||
    url(r'^qr_code/', views.StockItemQRCode.as_view(), name='stock-item-qr'),
 | 
					    url(r'^qr_code/', views.StockItemQRCode.as_view(), name='stock-item-qr'),
 | 
				
			||||||
    url(r'^delete_test_data/', views.StockItemDeleteTestData.as_view(), name='stock-item-delete-test-data'),
 | 
					    url(r'^delete_test_data/', views.StockItemDeleteTestData.as_view(), name='stock-item-delete-test-data'),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1027,89 +1027,6 @@ class StockLocationCreate(AjaxCreateView):
 | 
				
			|||||||
                    pass
 | 
					                    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StockItemSerialize(AjaxUpdateView):
 | 
					 | 
				
			||||||
    """ View for manually serializing a StockItem """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    model = StockItem
 | 
					 | 
				
			||||||
    ajax_template_name = 'stock/item_serialize.html'
 | 
					 | 
				
			||||||
    ajax_form_title = _('Serialize Stock')
 | 
					 | 
				
			||||||
    form_class = StockForms.SerializeStockForm
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_form(self):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        context = self.get_form_kwargs()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Pass the StockItem object through to the form
 | 
					 | 
				
			||||||
        context['item'] = self.get_object()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        form = StockForms.SerializeStockForm(**context)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return form
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_initial(self):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        initials = super().get_initial().copy()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        item = self.get_object()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        initials['quantity'] = item.quantity
 | 
					 | 
				
			||||||
        initials['serial_numbers'] = item.part.getSerialNumberString(item.quantity)
 | 
					 | 
				
			||||||
        if item.location is not None:
 | 
					 | 
				
			||||||
            initials['destination'] = item.location.pk
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return initials
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self, request, *args, **kwargs):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return super().get(request, *args, **kwargs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def post(self, request, *args, **kwargs):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        form = self.get_form()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        item = self.get_object()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        quantity = request.POST.get('quantity', 0)
 | 
					 | 
				
			||||||
        serials = request.POST.get('serial_numbers', '')
 | 
					 | 
				
			||||||
        dest_id = request.POST.get('destination', None)
 | 
					 | 
				
			||||||
        notes = request.POST.get('note', '')
 | 
					 | 
				
			||||||
        user = request.user
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        valid = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            destination = StockLocation.objects.get(pk=dest_id)
 | 
					 | 
				
			||||||
        except (ValueError, StockLocation.DoesNotExist):
 | 
					 | 
				
			||||||
            destination = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            numbers = extract_serial_numbers(serials, quantity)
 | 
					 | 
				
			||||||
        except ValidationError as e:
 | 
					 | 
				
			||||||
            form.add_error('serial_numbers', e.messages)
 | 
					 | 
				
			||||||
            valid = False
 | 
					 | 
				
			||||||
            numbers = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if valid:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                item.serializeStock(quantity, numbers, user, notes=notes, location=destination)
 | 
					 | 
				
			||||||
            except ValidationError as e:
 | 
					 | 
				
			||||||
                messages = e.message_dict
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                for k in messages.keys():
 | 
					 | 
				
			||||||
                    if k in ['quantity', 'destination', 'serial_numbers']:
 | 
					 | 
				
			||||||
                        form.add_error(k, messages[k])
 | 
					 | 
				
			||||||
                    else:
 | 
					 | 
				
			||||||
                        form.add_error(None, messages[k])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                valid = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        data = {
 | 
					 | 
				
			||||||
            'form_valid': valid,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return self.renderJsonResponse(request, form, data=data)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class StockItemCreate(AjaxCreateView):
 | 
					class StockItemCreate(AjaxCreateView):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    View for creating a new StockItem
 | 
					    View for creating a new StockItem
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,12 +52,39 @@
 | 
				
			|||||||
    loadStockTrackingTable,
 | 
					    loadStockTrackingTable,
 | 
				
			||||||
    loadTableFilters,
 | 
					    loadTableFilters,
 | 
				
			||||||
    removeStockRow,
 | 
					    removeStockRow,
 | 
				
			||||||
 | 
					    serializeStockItem,
 | 
				
			||||||
    stockItemFields,
 | 
					    stockItemFields,
 | 
				
			||||||
    stockLocationFields,
 | 
					    stockLocationFields,
 | 
				
			||||||
    stockStatusCodes,
 | 
					    stockStatusCodes,
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Launches a modal form to serialize a particular StockItem
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function serializeStockItem(pk, options={}) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var url = `/api/stock/${pk}/serialize/`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    options.method = 'POST';
 | 
				
			||||||
 | 
					    options.title = '{% trans "Serialize Stock Item" %}';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    options.fields = {
 | 
				
			||||||
 | 
					        quantity: {},
 | 
				
			||||||
 | 
					        serial_numbers: {
 | 
				
			||||||
 | 
					            icon: 'fa-hashtag',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        destination: {
 | 
				
			||||||
 | 
					            icon: 'fa-sitemap',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        notes: {},
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructForm(url, options);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function stockLocationFields(options={}) {
 | 
					function stockLocationFields(options={}) {
 | 
				
			||||||
    var fields = {
 | 
					    var fields = {
 | 
				
			||||||
        parent: {
 | 
					        parent: {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user