2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-10 23:14:13 +00:00

[Plugin] Allow custom plugins for running validation routines (#3776)

* Adds new plugin mixin for performing custom validation steps

* Adds simple test plugin for custom validation

* Run part name and IPN validators checks through loaded plugins

* Expose more validation functions to plugins:

- SalesOrder reference
- PurchaseOrder reference
- BuildOrder reference

* Remove custom validation of reference fields

- For now, this is too complex to consider given the current incrementing-reference implementation
- Might revisit this at a later stage.

* Custom validation of serial numbers:

- Replace "checkIfSerialNumberExists" method with "validate_serial_number"
- Pass serial number through to custom plugins
- General code / docstring improvements

* Update unit tests

* Update InvenTree/stock/tests.py

Co-authored-by: Matthias Mair <code@mjmair.com>

* Adds global setting to specify whether serial numbers must be unique globally

- Default is false to preserve behaviour

* Improved error message when attempting to create stock item with invalid serial numbers

* Add more detail to existing serial error message

* Add unit testing for serial number uniqueness

* Allow plugins to convert a serial number to an integer (for optimized sorting)

* Add placeholder plugin methods for incrementing and decrementing serial numbers

* Typo fix

* Add improved method for determining the "latest" serial number

* Remove calls to getLatestSerialNumber

* Update validate_serial_number method

- Add option to disable checking for duplicates
- Don't pass optional StockItem through to plugins

* Refactor serial number extraction methods

- Expose the "incrementing" portion to external plugins

* Bug fixes

* Update unit tests

* Fix for get_latest_serial_number

* Ensure custom serial integer values are clipped

* Adds a plugin for validating and generating hexadecimal serial numbers

* Update unit tests

* Add stub methods for batch code functionality

* remove "hex serials" plugin

- Was simply breaking unit tests

* Allow custom plugins to generate and validate batch codes

- Perform batch code validation when StockItem is saved
- Improve display of error message in modal forms

* Fix unit tests for stock app

* Log message if plugin has a duplicate slug

* Unit test fix

Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
Oliver
2022-10-18 23:50:07 +11:00
committed by GitHub
parent 269b269de3
commit 906ae218aa
24 changed files with 746 additions and 280 deletions

View File

@ -180,9 +180,24 @@ class StockItemManager(TreeManager):
def generate_batch_code():
"""Generate a default 'batch code' for a new StockItem.
This uses the value of the 'STOCK_BATCH_CODE_TEMPLATE' setting (if configured),
By default, this uses the value of the 'STOCK_BATCH_CODE_TEMPLATE' setting (if configured),
which can be passed through a simple template.
Also, this function is exposed to the ValidationMixin plugin class,
allowing custom plugins to be used to generate new batch code values
"""
# First, check if any plugins can generate batch codes
from plugin.registry import registry
for plugin in registry.with_mixin('validation'):
batch = plugin.generate_batch_code()
if batch is not None:
# Return the first non-null value generated by a plugin
return batch
# If we get to this point, no plugin was able to generate a new batch code
batch_template = common.models.InvenTreeSetting.get_setting('STOCK_BATCH_CODE_TEMPLATE', '')
now = datetime.now()
@ -260,15 +275,38 @@ class StockItem(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
This is used for efficient numerical sorting
"""
serial = getattr(self, 'serial', '')
# Default value if we cannot convert to an integer
serial_int = 0
serial = str(getattr(self, 'serial', '')).strip()
from plugin.registry import registry
# First, let any plugins convert this serial number to an integer value
# If a non-null value is returned (by any plugin) we will use that
serial_int = None
for plugin in registry.with_mixin('validation'):
serial_int = plugin.convert_serial_to_int(serial)
if serial_int is not None:
# Save the first returned result
# Ensure that it is clipped within a range allowed in the database schema
clip = 0x7fffffff
serial_int = abs(serial_int)
if serial_int > clip:
serial_int = clip
self.serial_int = serial_int
return
if serial is not None:
# If we get to this point, none of the available plugins provided an integer value
serial = str(serial).strip()
# Default value if we cannot convert to an integer
serial_int = 0
if serial not in [None, '']:
serial_int = extract_int(serial)
self.serial_int = serial_int
@ -408,16 +446,32 @@ class StockItem(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
# If the serial number is set, make sure it is not a duplicate
if self.serial:
# Query to look for duplicate serial numbers
parts = PartModels.Part.objects.filter(tree_id=self.part.tree_id)
stock = StockItem.objects.filter(part__in=parts, serial=self.serial)
# Exclude myself from the search
if self.pk is not None:
stock = stock.exclude(pk=self.pk)
self.serial = str(self.serial).strip()
try:
self.part.validate_serial_number(self.serial, self, raise_error=True)
except ValidationError as exc:
raise ValidationError({
'serial': exc.message,
})
if stock.exists():
raise ValidationError({"serial": _("StockItem with this serial number already exists")})
def validate_batch_code(self):
"""Ensure that the batch code is valid for this StockItem.
- Validation is performed by custom plugins.
- By default, no validation checks are performed
"""
from plugin.registry import registry
for plugin in registry.with_mixin('validation'):
try:
plugin.validate_batch_code(self.batch)
except ValidationError as exc:
raise ValidationError({
'batch': exc.message
})
def clean(self):
"""Validate the StockItem object (separate to field validation).
@ -438,6 +492,8 @@ class StockItem(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
if type(self.batch) is str:
self.batch = self.batch.strip()
self.validate_batch_code()
try:
# Trackable parts must have integer values for quantity field!
if self.part.trackable: