From 6144d7e209b07bc01153b05538f60396639b212c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 17 Nov 2020 15:29:44 +1100 Subject: [PATCH] Fix for StockItem creation form - purchase_price field was required (should not be!) - Fixed some validation issues - Cleaned up form implementation --- InvenTree/InvenTree/views.py | 15 +- InvenTree/stock/forms.py | 2 +- .../migrations/0055_auto_20201117_1453.py | 19 ++ InvenTree/stock/models.py | 1 + InvenTree/stock/views.py | 188 ++++++++---------- 5 files changed, 110 insertions(+), 115 deletions(-) create mode 100644 InvenTree/stock/migrations/0055_auto_20201117_1453.py diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index e61e2d497c..b2ef8f965a 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -388,14 +388,15 @@ class AjaxCreateView(AjaxMixin, CreateView): # Save the object to the database self.object = self.save(self.form) - # Return the PK of the newly-created object - data['pk'] = self.object.pk - data['text'] = str(self.object) + if self.object: + # Return the PK of the newly-created object + data['pk'] = self.object.pk + data['text'] = str(self.object) - try: - data['url'] = self.object.get_absolute_url() - except AttributeError: - pass + try: + data['url'] = self.object.get_absolute_url() + except AttributeError: + pass return self.renderJsonResponse(request, self.form, data) diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 77d0f58295..8ab88155e2 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -124,11 +124,11 @@ class CreateStockItemForm(HelperForm): fields = [ 'part', 'supplier_part', - 'purchase_price', 'location', 'quantity', 'batch', 'serial_numbers', + 'purchase_price', 'link', 'delete_on_deplete', 'status', diff --git a/InvenTree/stock/migrations/0055_auto_20201117_1453.py b/InvenTree/stock/migrations/0055_auto_20201117_1453.py new file mode 100644 index 0000000000..265347ae5c --- /dev/null +++ b/InvenTree/stock/migrations/0055_auto_20201117_1453.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.7 on 2020-11-17 03:53 + +from django.db import migrations +import djmoney.models.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0054_remove_stockitem_build_order'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitem', + name='purchase_price', + field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency='USD', help_text='Single unit purchase price at time of purchase', max_digits=19, null=True, verbose_name='Purchase Price'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 01b7b27cc4..d1e46c53a7 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -453,6 +453,7 @@ class StockItem(MPTTModel): max_digits=19, decimal_places=4, default_currency='USD', + blank=True, null=True, verbose_name=_('Purchase Price'), help_text=_('Single unit purchase price at time of purchase'), diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 88c2274ddc..175a06ae80 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -1607,127 +1607,101 @@ class StockItemCreate(AjaxCreateView): return initials - def post(self, request, *args, **kwargs): - """ Handle POST of StockItemCreate form. - - - Manage serial-number valdiation for tracked parts + def validate(self, item, form): + """ + Extra form validation steps """ - part = None + data = form.cleaned_data - form = self.get_form() + part = data['part'] + + quantity = data.get('quantity', None) - data = {} + if not quantity: + return - valid = form.is_valid() + try: + quantity = Decimal(quantity) + except (ValueError, InvalidOperation): + form.add_error('quantity', _('Invalid quantity provided')) + return - if valid: - part_id = form['part'].value() - try: - part = Part.objects.get(id=part_id) - quantity = Decimal(form['quantity'].value()) + if quantity < 0: + form.add_error('quantity', _('Quantity cannot be negative')) - except (Part.DoesNotExist, ValueError, InvalidOperation): - part = None - quantity = 1 - valid = False - form.add_error('quantity', _('Invalid quantity')) + # Trackable parts are treated differently + if part.trackable: + sn = data.get('serial_numbers', '') + sn = str(sn).strip() - if quantity < 0: - form.add_error('quantity', _('Quantity cannot be less than zero')) - valid = False + if len(sn) > 0: + serials = extract_serial_numbers(sn, quantity) - if part is None: - form.add_error('part', _('Invalid part selection')) + existing = part.find_conflicting_serial_numbers(serials) + + if len(existing) > 0: + exists = ','.join([str(x) for x in existing]) + + form.add_error( + 'serial_numbers', + _('Serial numbers already exist') + ': ' + exists + ) + + def save(self, form, **kwargs): + """ + Create a new StockItem based on the provided form data. + """ + + data = form.cleaned_data + + part = data['part'] + + quantity = data['quantity'] + + if part.trackable: + sn = data.get('serial_numbers', '') + sn = str(sn).strip() + + # Create a single stock item for each provided serial number + if len(sn) > 0: + serials = extract_serial_numbers(sn, quantity) + + for serial in serials: + item = StockItem( + part=part, + quantity=1, + serial=serial, + supplier_part=data.get('supplier_part', None), + location=data.get('location', None), + batch=data.get('batch', None), + delete_on_deplete=False, + status=data.get('status'), + link=data.get('link', ''), + ) + + item.save(user=self.request.user) + + # Create a single StockItem of the specified quantity else: - # A trackable part must provide serial numbesr - if part.trackable: - sn = request.POST.get('serial_numbers', '') + form._post_clean() - sn = str(sn).strip() + item = form.save(commit=False) + item.user = self.request.user + item.save() - # If user has specified a range of serial numbers - if len(sn) > 0: - try: - serials = extract_serial_numbers(sn, quantity) + return item + + # Non-trackable part + else: - existing = part.find_conflicting_serial_numbers(serials) + form._post_clean() + + item = form.save(commit=False) + item.user = self.request.user + item.save() - if len(existing) > 0: - exists = ",".join([str(x) for x in existing]) - form.add_error( - 'serial_numbers', - _('Serial numbers already exist') + ': ' + exists - ) - valid = False - - else: - # At this point we have a list of serial numbers which we know are valid, - # and do not currently exist - form.clean() - - form_data = form.cleaned_data - - if form.is_valid(): - - for serial in serials: - # Create a new stock item for each serial number - item = StockItem( - part=part, - quantity=1, - serial=serial, - supplier_part=form_data.get('supplier_part'), - location=form_data.get('location'), - batch=form_data.get('batch'), - delete_on_deplete=False, - status=form_data.get('status'), - link=form_data.get('link'), - ) - - item.save(user=request.user) - - data['success'] = _('Created {n} new stock items'.format(n=len(serials))) - valid = True - - except ValidationError as e: - form.add_error('serial_numbers', e.messages) - valid = False - - else: - # We have a serialized part, but no serial numbers specified... - form.clean() - form._post_clean() - - if form.is_valid(): - - item = form.save(commit=False) - item.save(user=request.user) - - data['pk'] = item.pk - data['url'] = item.get_absolute_url() - data['success'] = _("Created new stock item") - - valid = True - - else: # Referenced Part object is not marked as "trackable" - # For non-serialized items, simply save the form. - # We need to call _post_clean() here because it is prevented in the form implementation - form.clean() - form._post_clean() - - if form.is_valid: - item = form.save(commit=False) - item.save(user=request.user) - - data['pk'] = item.pk - data['url'] = item.get_absolute_url() - data['success'] = _("Created new stock item") - - valid = True - - data['form_valid'] = valid and form.is_valid() - - return self.renderJsonResponse(request, form, data=data) + return item class StockLocationDelete(AjaxDeleteView):