From 217097c9d3cf32281f01da8ef17166bd0e4b4ffd Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 30 Mar 2021 00:10:28 +1100 Subject: [PATCH] Add custom form template --- InvenTree/order/forms.py | 17 +++-- InvenTree/order/models.py | 2 +- .../order/so_allocate_by_serial.html | 12 ++++ InvenTree/order/views.py | 71 ++++++++++++++++++- 4 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 InvenTree/order/templates/order/so_allocate_by_serial.html 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):