def validate(self, build, form, **kwargs): """ Validation for the form: """ quantity = form.cleaned_data.get('output_quantity', None) serials = form.cleaned_data.get('serial_numbers', None) # Check that the serial numbers are valid if serials: try: extracted = extract_serial_numbers(serials, quantity) if extracted: # Check for conflicting serial numbers conflicts = build.part.find_conflicting_serial_numbers( extracted) if len(conflicts) > 0: msg = ",".join([str(c) for c in conflicts]) form.add_error( 'serial_numbers', _('Serial numbers already exist') + ': ' + msg, ) except ValidationError as e: form.add_error('serial_numbers', e.messages) else: # If no serial numbers are provided, should they be? if build.part.trackable: form.add_error( 'serial_numbers', _('Serial numbers required for trackable build output'))
def validate(self, data): data = super().validate(data) line_item = data['line_item'] quantity = data['quantity'] serial_numbers = data.get('serial_numbers', '').strip() base_part = line_item.part.part # Does the quantity need to be "integer" (for trackable parts?) if base_part.trackable: if Decimal(quantity) != int(quantity): raise ValidationError({ 'quantity': _('An integer quantity must be provided for trackable parts' ), }) # If serial numbers are provided if serial_numbers: try: # Pass the serial numbers through to the parent serializer once validated data['serials'] = extract_serial_numbers( serial_numbers, quantity, base_part.getLatestSerialNumberInt()) except DjangoValidationError as e: raise ValidationError({ 'serial_numbers': e.messages, }) return data
def validate(self, item, form): """ Extra form validation steps """ data = form.cleaned_data part = data.get('part', None) quantity = data.get('quantity', None) owner = data.get('owner', None) if not part: return if not quantity: return try: quantity = Decimal(quantity) except (ValueError, InvalidOperation): form.add_error('quantity', _('Invalid quantity provided')) return if quantity < 0: form.add_error('quantity', _('Quantity cannot be negative')) # Trackable parts are treated differently if part.trackable: sn = data.get('serial_numbers', '') sn = str(sn).strip() if len(sn) > 0: try: serials = extract_serial_numbers(sn, quantity) except ValidationError as e: serials = None form.add_error('serial_numbers', e.messages) if serials is not None: 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 ) # Is ownership control enabled? stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') if stock_ownership_control: # Check if owner is set if not owner and not self.request.user.is_superuser: form.add_error('owner', _('Owner is required (ownership control is enabled)')) return
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: form._post_clean() item = form.save(commit=False) item.user = self.request.user item.save(user=self.request.user) return item # Non-trackable part else: form._post_clean() item = form.save(commit=False) item.user = self.request.user item.save(user=self.request.user) return item
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)
def validate(self, build, form, **kwargs): """ Validation for the form: """ quantity = form.cleaned_data.get('output_quantity', None) serials = form.cleaned_data.get('serial_numbers', None) if quantity: build = self.get_object() # Check that requested output don't exceed build remaining quantity maximum_output = int(build.remaining - build.incomplete_count) if quantity > maximum_output: form.add_error( 'output_quantity', _('Maximum output quantity is ') + str(maximum_output), ) # Check that the serial numbers are valid if serials: try: extracted = extract_serial_numbers(serials, quantity) if extracted: # Check for conflicting serial numbers conflicts = build.part.find_conflicting_serial_numbers( extracted) if len(conflicts) > 0: msg = ",".join([str(c) for c in conflicts]) form.add_error( 'serial_numbers', _('Serial numbers already exist') + ': ' + msg, ) except ValidationError as e: form.add_error('serial_numbers', e.messages) else: # If no serial numbers are provided, should they be? if build.part.trackable: form.add_error( 'serial_numbers', _('Serial numbers required for trackable build output'))
def validate(self, data): """ Perform form validation """ part = self.get_part() # Cache a list of serial numbers (to be used in the "save" method) self.serials = None quantity = data['quantity'] serial_numbers = data.get('serial_numbers', '') if serial_numbers: try: self.serials = extract_serial_numbers( serial_numbers, quantity, part.getLatestSerialNumberInt()) except DjangoValidationError as e: raise ValidationError({ 'serial_numbers': e.messages, }) # Check for conflicting serial numbesr existing = [] for serial in self.serials: if part.checkIfSerialNumberExists(serial): existing.append(serial) if len(existing) > 0: msg = _("The following serial numbers already exist") msg += " : " msg += ",".join([str(e) for e in existing]) raise ValidationError({ 'serial_numbers': msg, }) return data
def save(self, build, form, **kwargs): """ Create a new build output """ data = form.cleaned_data quantity = data.get('output_quantity', None) batch = data.get('batch', None) serials = data.get('serial_numbers', None) if serials: serial_numbers = extract_serial_numbers(serials, quantity) else: serial_numbers = None build.create_build_output( quantity, serials=serial_numbers, batch=batch, )
def validate(self, item, form): """ Extra form validation steps """ data = form.cleaned_data part = data['part'] quantity = data.get('quantity', None) if not quantity: return try: quantity = Decimal(quantity) except (ValueError, InvalidOperation): form.add_error('quantity', _('Invalid quantity provided')) return if quantity < 0: form.add_error('quantity', _('Quantity cannot be negative')) # Trackable parts are treated differently if part.trackable: sn = data.get('serial_numbers', '') sn = str(sn).strip() if len(sn) > 0: serials = extract_serial_numbers(sn, quantity) 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 validate(self, data): """ Validation for the serializer: - Ensure the serial_numbers and quantity fields match - Check that all serial numbers exist - Check that the serial numbers are not yet allocated """ data = super().validate(data) line_item = data['line_item'] quantity = data['quantity'] serial_numbers = data['serial_numbers'] part = line_item.part try: data['serials'] = extract_serial_numbers(serial_numbers, quantity) except DjangoValidationError as e: raise ValidationError({ 'serial_numbers': e.messages, }) serials_not_exist = [] serials_allocated = [] stock_items_to_allocate = [] for serial in data['serials']: items = stock.models.StockItem.objects.filter( part=part, serial=serial, quantity=1, ) if not items.exists(): serials_not_exist.append(str(serial)) continue stock_item = items[0] if stock_item.unallocated_quantity() == 1: stock_items_to_allocate.append(stock_item) else: serials_allocated.append(str(serial)) if len(serials_not_exist) > 0: error_msg = _("No match found for the following serial numbers") error_msg += ": " error_msg += ",".join(serials_not_exist) raise ValidationError({ 'serial_numbers': error_msg }) if len(serials_allocated) > 0: error_msg = _("The following serial numbers are already allocated") error_msg += ": " error_msg += ",".join(serials_allocated) raise ValidationError({ 'serial_numbers': error_msg, }) data['stock_items'] = stock_items_to_allocate return data
def validate(self): data = self.form.cleaned_data # Extract hidden fields from posted data self.line = data.get('line', None) self.part = data.get('part', None) if self.line: self.form.fields['line'].widget = HiddenInput() else: self.form.add_error('line', _('Select line item')) if self.part: self.form.fields['part'].widget = HiddenInput() else: self.form.add_error('part', _('Select part')) 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 create(self, request, *args, **kwargs): """ Create a new StockItem object via the API. We override the default 'create' implementation. If a location is *not* specified, but the linked *part* has a default location, we can pre-fill the location automatically. """ user = request.user # Copy the request data, to side-step "mutability" issues data = OrderedDict() data.update(request.data) quantity = data.get('quantity', None) if quantity is None: raise ValidationError({ 'quantity': _('Quantity is required'), }) try: part = Part.objects.get(pk=data.get('part', None)) except (ValueError, Part.DoesNotExist): raise ValidationError({ 'part': _('Valid part must be supplied'), }) # Set default location (if not provided) if 'location' not in data: location = part.get_default_location() if location: data['location'] = location.pk # An expiry date was *not* specified - try to infer it! if 'expiry_date' not in data and part.default_expiry > 0: data['expiry_date'] = datetime.now().date() + timedelta( days=part.default_expiry) # Attempt to extract serial numbers from submitted data serials = None # Check if a set of serial numbers was provided serial_numbers = data.get('serial_numbers', '') # Assign serial numbers for a trackable part if serial_numbers: if not part.trackable: raise ValidationError({ 'serial_numbers': [ _("Serial numbers cannot be supplied for a non-trackable part" ) ] }) # If serial numbers are specified, check that they match! try: serials = extract_serial_numbers( serial_numbers, quantity, part.getLatestSerialNumberInt()) # Determine if any of the specified serial numbers already exist! existing = [] for serial in serials: if part.checkIfSerialNumberExists(serial): existing.append(serial) if len(existing) > 0: msg = _("The following serial numbers already exist") msg += " : " msg += ",".join([str(e) for e in existing]) raise ValidationError({ 'serial_numbers': [msg], }) except DjangoValidationError as e: raise ValidationError({ 'quantity': e.messages, 'serial_numbers': e.messages, }) if serials is not None: """ If the stock item is going to be serialized, set the quantity to 1 """ data['quantity'] = 1 # De-serialize the provided data serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) with transaction.atomic(): # Create an initial StockItem object item = serializer.save() if serials: # Assign the first serial number to the "master" item item.serial = serials[0] # Save the item (with user information) item.save(user=user) if serials: for serial in serials[1:]: # Create a duplicate stock item with the next serial number item.pk = None item.serial = serial item.save(user=user) response_data = { 'quantity': quantity, 'serial_numbers': serials, } else: response_data = serializer.data return Response(response_data, status=status.HTTP_201_CREATED, headers=self.get_success_headers(serializer.data))
def create(self, request, *args, **kwargs): """ Create a new StockItem object via the API. We override the default 'create' implementation. If a location is *not* specified, but the linked *part* has a default location, we can pre-fill the location automatically. """ user = request.user data = request.data serializer = self.get_serializer(data=data) serializer.is_valid(raise_exception=True) # Check if a set of serial numbers was provided serial_numbers = data.get('serial_numbers', '') quantity = data.get('quantity', None) if quantity is None: raise ValidationError({ 'quantity': _('Quantity is required'), }) notes = data.get('notes', '') serials = None if serial_numbers: # If serial numbers are specified, check that they match! try: serials = extract_serial_numbers(serial_numbers, data['quantity']) except DjangoValidationError as e: raise ValidationError({ 'quantity': e.messages, 'serial_numbers': e.messages, }) with transaction.atomic(): # Create an initial stock item item = serializer.save() # A location was *not* specified - try to infer it if 'location' not in data: item.location = item.part.get_default_location() # An expiry date was *not* specified - try to infer it! if 'expiry_date' not in data: if item.part.default_expiry > 0: item.expiry_date = datetime.now().date() + timedelta( days=item.part.default_expiry) # Finally, save the item (with user information) item.save(user=user) if serials: """ Serialize the stock, if required - Note that the "original" stock item needs to be created first, so it can be serialized - It is then immediately deleted """ try: item.serializeStock( quantity, serials, user, notes=notes, location=item.location, ) headers = self.get_success_headers(serializer.data) # Delete the original item item.delete() response_data = { 'quantity': quantity, 'serial_numbers': serials, } return Response(response_data, status=status.HTTP_201_CREATED, headers=headers) except DjangoValidationError as e: raise ValidationError({ 'quantity': e.messages, 'serial_numbers': e.messages, }) # Return a response headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)