diff --git a/InvenTree/order/forms.py b/InvenTree/order/forms.py
index 8a1b8ce8ba..bb6cb40889 100644
--- a/InvenTree/order/forms.py
+++ b/InvenTree/order/forms.py
@@ -220,25 +220,34 @@ class AllocateSerialsToSalesOrderForm(forms.Form):
"""
line = forms.ModelChoiceField(
- queryset = SalesOrderLineItem.objects.all(),
+ queryset=SalesOrderLineItem.objects.all(),
)
part = forms.ModelChoiceField(
- queryset = part.models.Part.objects.all(),
+ queryset=part.models.Part.objects.all(),
)
serials = forms.CharField(
label=_("Serial Numbers"),
- required=False,
+ required=True,
help_text=_('Enter stock item serial numbers'),
)
+ quantity = forms.IntegerField(
+ label=_('Quantity'),
+ required=True,
+ help_text=_('Enter quantity of stock items'),
+ initial=1,
+ min_value=1
+ )
+
class Meta:
fields = [
'line',
'part',
'serials',
+ 'quantity',
]
@@ -247,7 +256,7 @@ class CreateSalesOrderAllocationForm(HelperForm):
Form for creating a SalesOrderAllocation item.
"""
- quantity = RoundingDecimalFormField(max_digits = 10, decimal_places=5)
+ quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5)
class Meta:
model = SalesOrderAllocation
diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py
index b621cda156..6bd79c20ed 100644
--- a/InvenTree/order/models.py
+++ b/InvenTree/order/models.py
@@ -736,7 +736,7 @@ class SalesOrderAllocation(models.Model):
if not self.item:
raise ValidationError({'item': _('Stock item has not been assigned')})
except stock_models.StockItem.DoesNotExist:
- raise ValidationError({'item': _('Stock item has not been assigned')})
+ raise ValidationError({'item': _('Stock item has not been assigned')})
try:
if not self.line.part == self.item.part:
diff --git a/InvenTree/order/templates/order/so_allocate_by_serial.html b/InvenTree/order/templates/order/so_allocate_by_serial.html
new file mode 100644
index 0000000000..3e11d658c7
--- /dev/null
+++ b/InvenTree/order/templates/order/so_allocate_by_serial.html
@@ -0,0 +1,12 @@
+{% extends "modal_form.html" %}
+{% load i18n %}
+
+{% block pre_form_content %}
+
+
+ {% include "hover_image.html" with image=part.image hover=true %}{{ part }}
+
+ {% trans "Allocate stock items by serial number" %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py
index 02b9c10e8b..bdf0407603 100644
--- a/InvenTree/order/views.py
+++ b/InvenTree/order/views.py
@@ -7,6 +7,7 @@ from __future__ import unicode_literals
from django.db import transaction
from django.shortcuts import get_object_or_404
+from django.core.exceptions import ValidationError
from django.urls import reverse
from django.utils.translation import ugettext as _
from django.views.generic import DetailView, ListView, UpdateView
@@ -31,6 +32,7 @@ from . import forms as order_forms
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
from InvenTree.helpers import DownloadFile, str2bool
+from InvenTree.helpers import extract_serial_numbers
from InvenTree.views import InvenTreeRoleMixin
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus, StockStatus
@@ -1300,6 +1302,7 @@ class SalesOrderAssignSerials(AjaxView, FormMixin):
model = SalesOrderAllocation
role_required = 'sales_order.change'
+ ajax_template_name = 'order/so_allocate_by_serial.html'
ajax_form_title = _('Allocate Serial Numbers')
form_class = order_forms.AllocateSerialsToSalesOrderForm
@@ -1338,10 +1341,14 @@ class SalesOrderAssignSerials(AjaxView, FormMixin):
valid = self.form.is_valid()
+ if valid:
+ self.allocate_items()
+
data = {
'form_valid': valid,
'form_errors': self.form.errors.as_json(),
'non_field_errors': self.form.non_field_errors().as_json(),
+ 'success': _("Allocated") + f" {len(self.stock_items)} " + _("items")
}
return self.renderJsonResponse(request, self.form, data)
@@ -1364,7 +1371,69 @@ class SalesOrderAssignSerials(AjaxView, FormMixin):
else:
self.form.add_error('part', _('Select part'))
- self.form.add_error('serials', 'abcde')
+ if not self.form.is_valid():
+ return
+
+ # Form is otherwise valid - check serial numbers
+ serials = data.get('serials', '')
+ quantity = data.get('quantity', 1)
+
+ # Save a list of serial_numbers
+ self.serial_numbers = None
+ self.stock_items = []
+
+ try:
+ self.serial_numbers = extract_serial_numbers(serials, quantity)
+
+ for serial in self.serial_numbers:
+ try:
+ # Find matching stock item
+ stock_item = StockItem.objects.get(
+ part=self.part,
+ serial=serial
+ )
+ except StockItem.DoesNotExist:
+ self.form.add_error(
+ 'serials',
+ _('No matching item for serial') + f" '{serial}'"
+ )
+ continue
+
+ # Now we have a valid stock item - but can it be added to the sales order?
+
+ # If not in stock, cannot be added to the order
+ if not stock_item.in_stock:
+ self.form.add_error(
+ 'serials',
+ f"'{serial}' " + _("is not in stock")
+ )
+ continue
+
+ # Already allocated to an order
+ if stock_item.is_allocated():
+ self.form.add_error(
+ 'serials',
+ f"'{serial}' " + _("already allocated to an order")
+ )
+ continue
+
+ # Add it to the list!
+ self.stock_items.append(stock_item)
+
+ except ValidationError as e:
+ self.form.add_error('serials', e.messages)
+
+ def allocate_items(self):
+ """
+ Create stock item allocations for each selected serial number
+ """
+
+ for stock_item in self.stock_items:
+ SalesOrderAllocation.objects.create(
+ item=stock_item,
+ line=self.line,
+ quantity=1,
+ )
def get_form(self):